GUACAMOLE-623: Merge support for terminals of containers in Kubernetes pods.
This commit is contained in:
commit
43db1965ef
@ -52,6 +52,10 @@ if ENABLE_PULSE
|
||||
SUBDIRS += src/pulse
|
||||
endif
|
||||
|
||||
if ENABLE_KUBERNETES
|
||||
SUBDIRS += src/protocols/kubernetes
|
||||
endif
|
||||
|
||||
if ENABLE_RDP
|
||||
SUBDIRS += src/protocols/rdp
|
||||
endif
|
||||
|
76
configure.ac
76
configure.ac
@ -1172,6 +1172,62 @@ fi
|
||||
AM_CONDITIONAL([ENABLE_WEBP], [test "x${have_webp}" = "xyes"])
|
||||
AC_SUBST(WEBP_LIBS)
|
||||
|
||||
#
|
||||
# libwebsockets
|
||||
#
|
||||
|
||||
have_libwebsockets=disabled
|
||||
WEBSOCKETS_LIBS=
|
||||
AC_ARG_WITH([websockets],
|
||||
[AS_HELP_STRING([--with-websockets],
|
||||
[support WebSockets @<:@default=check@:>@])],
|
||||
[],
|
||||
[with_websockets=check])
|
||||
|
||||
if test "x$with_websockets" != "xno"
|
||||
then
|
||||
have_libwebsockets=yes
|
||||
AC_CHECK_LIB([websockets],
|
||||
[lws_create_context],
|
||||
[WEBSOCKETS_LIBS="$WEBSOCKETS_LIBS -lwebsockets"],
|
||||
[AC_MSG_WARN([
|
||||
--------------------------------------------
|
||||
Unable to find libwebsockets.
|
||||
Support for Kubernetes will be disabled.
|
||||
--------------------------------------------])
|
||||
have_libwebsockets=no])
|
||||
fi
|
||||
|
||||
# Check for client-specific closed event, which must be used in favor of the
|
||||
# generic closed event if libwebsockets is recent enough to provide this
|
||||
if test "x$with_websockets" != "xno"
|
||||
then
|
||||
AC_CHECK_DECL([LWS_CALLBACK_CLIENT_CLOSED],
|
||||
[AC_DEFINE([HAVE_LWS_CALLBACK_CLIENT_CLOSED],,
|
||||
[Whether LWS_CALLBACK_CLIENT_CLOSED is defined])],,
|
||||
[#include <libwebsockets.h>])
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL([ENABLE_WEBSOCKETS],
|
||||
[test "x${have_libwebsockets}" = "xyes"])
|
||||
|
||||
AC_SUBST(WEBSOCKETS_LIBS)
|
||||
|
||||
#
|
||||
# Kubernetes
|
||||
#
|
||||
|
||||
AC_ARG_ENABLE([kubernetes],
|
||||
[AS_HELP_STRING([--disable-kubernetes],
|
||||
[do not build support for attaching to Kubernetes pods])],
|
||||
[],
|
||||
[enable_kubernetes=yes])
|
||||
|
||||
AM_CONDITIONAL([ENABLE_KUBERNETES], [test "x${enable_kubernetes}" = "xyes" \
|
||||
-a "x${have_libwebsockets}" = "xyes" \
|
||||
-a "x${have_ssl}" = "xyes" \
|
||||
-a "x${have_terminal}" = "xyes"])
|
||||
|
||||
#
|
||||
# guacd
|
||||
#
|
||||
@ -1230,6 +1286,7 @@ AC_CONFIG_FILES([Makefile
|
||||
src/guaclog/Makefile
|
||||
src/guaclog/man/guaclog.1
|
||||
src/pulse/Makefile
|
||||
src/protocols/kubernetes/Makefile
|
||||
src/protocols/rdp/Makefile
|
||||
src/protocols/ssh/Makefile
|
||||
src/protocols/telnet/Makefile
|
||||
@ -1240,10 +1297,11 @@ AC_OUTPUT
|
||||
# Protocol build status
|
||||
#
|
||||
|
||||
AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no])
|
||||
AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no])
|
||||
AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no])
|
||||
AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no])
|
||||
AM_COND_IF([ENABLE_KUBERNETES], [build_kubernetes=yes], [build_kubernetes=no])
|
||||
AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no])
|
||||
AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no])
|
||||
AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no])
|
||||
AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no])
|
||||
|
||||
#
|
||||
# Service / tool build status
|
||||
@ -1287,15 +1345,17 @@ $PACKAGE_NAME version $PACKAGE_VERSION
|
||||
libVNCServer ........ ${have_libvncserver}
|
||||
libvorbis ........... ${have_vorbis}
|
||||
libpulse ............ ${have_pulse}
|
||||
libwebsockets ....... ${have_libwebsockets}
|
||||
libwebp ............. ${have_webp}
|
||||
wsock32 ............. ${have_winsock}
|
||||
|
||||
Protocol support:
|
||||
|
||||
RDP ....... ${build_rdp}
|
||||
SSH ....... ${build_ssh}
|
||||
Telnet .... ${build_telnet}
|
||||
VNC ....... ${build_vnc}
|
||||
Kubernetes .... ${build_kubernetes}
|
||||
RDP ........... ${build_rdp}
|
||||
SSH ........... ${build_ssh}
|
||||
Telnet ........ ${build_telnet}
|
||||
VNC ........... ${build_vnc}
|
||||
|
||||
Services / tools:
|
||||
|
||||
|
64
src/protocols/kubernetes/Makefile.am
Normal file
64
src/protocols/kubernetes/Makefile.am
Normal file
@ -0,0 +1,64 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
lib_LTLIBRARIES = libguac-client-kubernetes.la
|
||||
|
||||
libguac_client_kubernetes_la_SOURCES = \
|
||||
client.c \
|
||||
clipboard.c \
|
||||
input.c \
|
||||
io.c \
|
||||
pipe.c \
|
||||
settings.c \
|
||||
ssl.c \
|
||||
kubernetes.c \
|
||||
url.c \
|
||||
user.c
|
||||
|
||||
noinst_HEADERS = \
|
||||
client.h \
|
||||
clipboard.h \
|
||||
input.h \
|
||||
io.h \
|
||||
pipe.h \
|
||||
settings.h \
|
||||
ssl.h \
|
||||
kubernetes.h \
|
||||
url.h \
|
||||
user.h
|
||||
|
||||
libguac_client_kubernetes_la_CFLAGS = \
|
||||
-Werror -Wall -Iinclude \
|
||||
@LIBGUAC_INCLUDE@ \
|
||||
@TERMINAL_INCLUDE@
|
||||
|
||||
libguac_client_kubernetes_la_LIBADD = \
|
||||
@COMMON_LTLIB@ \
|
||||
@LIBGUAC_LTLIB@ \
|
||||
@TERMINAL_LTLIB@
|
||||
|
||||
libguac_client_kubernetes_la_LDFLAGS = \
|
||||
-version-info 0:0:0 \
|
||||
@PTHREAD_LIBS@ \
|
||||
@SSL_LIBS@ \
|
||||
@WEBSOCKETS_LIBS@
|
||||
|
133
src/protocols/kubernetes/client.c
Normal file
133
src/protocols/kubernetes/client.c
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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 "client.h"
|
||||
#include "common/clipboard.h"
|
||||
#include "kubernetes.h"
|
||||
#include "settings.h"
|
||||
#include "user.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <langinfo.h>
|
||||
#include <locale.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
guac_client* guac_kubernetes_lws_current_client = NULL;
|
||||
|
||||
/**
|
||||
* Logging callback invoked by libwebsockets to log a single line of logging
|
||||
* output. As libwebsockets messages are all generally low-level, the log
|
||||
* level provided by libwebsockets is ignored here, with all messages logged
|
||||
* instead at guacd's debug level.
|
||||
*
|
||||
* @param level
|
||||
* The libwebsockets log level associated with the log message. This value
|
||||
* is ignored by this implementation of the logging callback.
|
||||
*
|
||||
* @param line
|
||||
* The line of logging output to log.
|
||||
*/
|
||||
static void guac_kubernetes_log(int level, const char* line) {
|
||||
|
||||
char buffer[1024];
|
||||
|
||||
/* Drop log message if there's nowhere to log yet */
|
||||
if (guac_kubernetes_lws_current_client == NULL)
|
||||
return;
|
||||
|
||||
/* Trim length of line to fit buffer (plus null terminator) */
|
||||
int length = strlen(line);
|
||||
if (length > sizeof(buffer) - 1)
|
||||
length = sizeof(buffer) - 1;
|
||||
|
||||
/* Copy as much of the received line as will fit in the buffer */
|
||||
memcpy(buffer, line, length);
|
||||
|
||||
/* If the line ends with a newline character, trim the character */
|
||||
if (length > 0 && buffer[length - 1] == '\n')
|
||||
length--;
|
||||
|
||||
/* Null-terminate the trimmed string */
|
||||
buffer[length] = '\0';
|
||||
|
||||
/* Log using guacd's own log facilities */
|
||||
guac_client_log(guac_kubernetes_lws_current_client, GUAC_LOG_DEBUG,
|
||||
"libwebsockets: %s", buffer);
|
||||
|
||||
}
|
||||
|
||||
int guac_client_init(guac_client* client) {
|
||||
|
||||
/* Ensure reference to main guac_client remains available in all
|
||||
* libwebsockets contexts */
|
||||
guac_kubernetes_lws_current_client = client;
|
||||
|
||||
/* Redirect libwebsockets logging */
|
||||
lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO,
|
||||
guac_kubernetes_log);
|
||||
|
||||
/* Set client args */
|
||||
client->args = GUAC_KUBERNETES_CLIENT_ARGS;
|
||||
|
||||
/* Allocate client instance data */
|
||||
guac_kubernetes_client* kubernetes_client = calloc(1, sizeof(guac_kubernetes_client));
|
||||
client->data = kubernetes_client;
|
||||
|
||||
/* Init clipboard */
|
||||
kubernetes_client->clipboard = guac_common_clipboard_alloc(GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH);
|
||||
|
||||
/* Set handlers */
|
||||
client->join_handler = guac_kubernetes_user_join_handler;
|
||||
client->free_handler = guac_kubernetes_client_free_handler;
|
||||
|
||||
/* Set locale and warn if not UTF-8 */
|
||||
setlocale(LC_CTYPE, "");
|
||||
if (strcmp(nl_langinfo(CODESET), "UTF-8") != 0) {
|
||||
guac_client_log(client, GUAC_LOG_INFO,
|
||||
"Current locale does not use UTF-8. Some characters may "
|
||||
"not render correctly.");
|
||||
}
|
||||
|
||||
/* Success */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_kubernetes_client_free_handler(guac_client* client) {
|
||||
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Wait client thread to terminate */
|
||||
pthread_join(kubernetes_client->client_thread, NULL);
|
||||
|
||||
/* Free settings */
|
||||
if (kubernetes_client->settings != NULL)
|
||||
guac_kubernetes_settings_free(kubernetes_client->settings);
|
||||
|
||||
guac_common_clipboard_free(kubernetes_client->clipboard);
|
||||
free(kubernetes_client);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
44
src/protocols/kubernetes/client.h
Normal file
44
src/protocols/kubernetes/client.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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_CLIENT_H
|
||||
#define GUAC_KUBERNETES_CLIENT_H
|
||||
|
||||
#include <guacamole/client.h>
|
||||
|
||||
/**
|
||||
* The maximum number of bytes to allow within the clipboard.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH 262144
|
||||
|
||||
/**
|
||||
* Static reference to the guac_client associated with the active Kubernetes
|
||||
* connection. While libwebsockets provides some means of storing and
|
||||
* retrieving custom data in some structures, this is not always available.
|
||||
*/
|
||||
extern guac_client* guac_kubernetes_lws_current_client;
|
||||
|
||||
/**
|
||||
* Free handler. Required by libguac and called when the guac_client is
|
||||
* disconnected and must be cleaned up.
|
||||
*/
|
||||
guac_client_free_handler guac_kubernetes_client_free_handler;
|
||||
|
||||
#endif
|
||||
|
65
src/protocols/kubernetes/clipboard.c
Normal file
65
src/protocols/kubernetes/clipboard.c
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 "clipboard.h"
|
||||
#include "common/clipboard.h"
|
||||
#include "kubernetes.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/stream.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
int guac_kubernetes_clipboard_handler(guac_user* user, guac_stream* stream,
|
||||
char* mimetype) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Clear clipboard and prepare for new data */
|
||||
guac_common_clipboard_reset(kubernetes_client->clipboard, mimetype);
|
||||
|
||||
/* Set handlers for clipboard stream */
|
||||
stream->blob_handler = guac_kubernetes_clipboard_blob_handler;
|
||||
stream->end_handler = guac_kubernetes_clipboard_end_handler;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int guac_kubernetes_clipboard_blob_handler(guac_user* user,
|
||||
guac_stream* stream, void* data, int length) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Append new data */
|
||||
guac_common_clipboard_append(kubernetes_client->clipboard, data, length);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int guac_kubernetes_clipboard_end_handler(guac_user* user,
|
||||
guac_stream* stream) {
|
||||
|
||||
/* Nothing to do - clipboard is implemented within client */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
41
src/protocols/kubernetes/clipboard.h
Normal file
41
src/protocols/kubernetes/clipboard.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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_CLIPBOARD_H
|
||||
#define GUAC_KUBERNETES_CLIPBOARD_H
|
||||
|
||||
#include <guacamole/user.h>
|
||||
|
||||
/**
|
||||
* Handler for inbound clipboard streams.
|
||||
*/
|
||||
guac_user_clipboard_handler guac_kubernetes_clipboard_handler;
|
||||
|
||||
/**
|
||||
* Handler for data received along clipboard streams.
|
||||
*/
|
||||
guac_user_blob_handler guac_kubernetes_clipboard_blob_handler;
|
||||
|
||||
/**
|
||||
* Handler for end-of-stream related to clipboard.
|
||||
*/
|
||||
guac_user_end_handler guac_kubernetes_clipboard_end_handler;
|
||||
|
||||
#endif
|
||||
|
94
src/protocols/kubernetes/input.c
Normal file
94
src/protocols/kubernetes/input.c
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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 "common/recording.h"
|
||||
#include "input.h"
|
||||
#include "kubernetes.h"
|
||||
#include "terminal/terminal.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
int guac_kubernetes_user_mouse_handler(guac_user* user,
|
||||
int x, int y, int mask) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Skip if terminal not yet ready */
|
||||
guac_terminal* term = kubernetes_client->term;
|
||||
if (term == NULL)
|
||||
return 0;
|
||||
|
||||
/* Report mouse position within recording */
|
||||
if (kubernetes_client->recording != NULL)
|
||||
guac_common_recording_report_mouse(kubernetes_client->recording, x, y,
|
||||
mask);
|
||||
|
||||
guac_terminal_send_mouse(term, user, x, y, mask);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_kubernetes_user_key_handler(guac_user* user, int keysym, int pressed) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Report key state within recording */
|
||||
if (kubernetes_client->recording != NULL)
|
||||
guac_common_recording_report_key(kubernetes_client->recording,
|
||||
keysym, pressed);
|
||||
|
||||
/* Skip if terminal not yet ready */
|
||||
guac_terminal* term = kubernetes_client->term;
|
||||
if (term == NULL)
|
||||
return 0;
|
||||
|
||||
guac_terminal_send_key(term, keysym, pressed);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_kubernetes_user_size_handler(guac_user* user, int width, int height) {
|
||||
|
||||
/* Get terminal */
|
||||
guac_client* client = user->client;
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Skip if terminal not yet ready */
|
||||
guac_terminal* terminal = kubernetes_client->term;
|
||||
if (terminal == NULL)
|
||||
return 0;
|
||||
|
||||
/* Resize terminal */
|
||||
guac_terminal_resize(terminal, width, height);
|
||||
|
||||
/* Update Kubernetes terminal window size if connected */
|
||||
guac_kubernetes_resize(client, terminal->term_height,
|
||||
terminal->term_width);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
44
src/protocols/kubernetes/input.h
Normal file
44
src/protocols/kubernetes/input.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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_INPUT_H
|
||||
#define GUAC_KUBERNETES_INPUT_H
|
||||
|
||||
#include <guacamole/user.h>
|
||||
|
||||
/**
|
||||
* Handler for key events. Required by libguac and called whenever key events
|
||||
* are received.
|
||||
*/
|
||||
guac_user_key_handler guac_kubernetes_user_key_handler;
|
||||
|
||||
/**
|
||||
* Handler for mouse events. Required by libguac and called whenever mouse
|
||||
* events are received.
|
||||
*/
|
||||
guac_user_mouse_handler guac_kubernetes_user_mouse_handler;
|
||||
|
||||
/**
|
||||
* Handler for size events. Required by libguac and called whenever the remote
|
||||
* display (window) is resized.
|
||||
*/
|
||||
guac_user_size_handler guac_kubernetes_user_size_handler;
|
||||
|
||||
#endif
|
||||
|
143
src/protocols/kubernetes/io.c
Normal file
143
src/protocols/kubernetes/io.c
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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 "kubernetes.h"
|
||||
#include "terminal/terminal.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
void guac_kubernetes_receive_data(guac_client* client,
|
||||
const char* buffer, size_t length) {
|
||||
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Strip channel index from beginning of buffer */
|
||||
int channel = *(buffer++);
|
||||
length--;
|
||||
|
||||
switch (channel) {
|
||||
|
||||
/* Write STDOUT / STDERR directly to terminal as output */
|
||||
case GUAC_KUBERNETES_CHANNEL_STDOUT:
|
||||
case GUAC_KUBERNETES_CHANNEL_STDERR:
|
||||
guac_terminal_write(kubernetes_client->term, buffer, length);
|
||||
break;
|
||||
|
||||
/* Ignore data on other channels */
|
||||
default:
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "Received %i bytes along "
|
||||
"channel %i.", length, channel);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void guac_kubernetes_send_message(guac_client* client,
|
||||
int channel, const char* data, int length) {
|
||||
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
pthread_mutex_lock(&(kubernetes_client->outbound_message_lock));
|
||||
|
||||
/* Add message to buffer if space is available */
|
||||
if (kubernetes_client->outbound_messages_waiting
|
||||
< GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES) {
|
||||
|
||||
/* Calculate storage position of next message */
|
||||
int index = (kubernetes_client->outbound_messages_top
|
||||
+ kubernetes_client->outbound_messages_waiting)
|
||||
% GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES;
|
||||
|
||||
/* Obtain pointer to message slot at calculated position */
|
||||
guac_kubernetes_message* message =
|
||||
&(kubernetes_client->outbound_messages[index]);
|
||||
|
||||
/* Copy details of message into buffer */
|
||||
message->channel = channel;
|
||||
memcpy(message->data, data, length);
|
||||
message->length = length;
|
||||
|
||||
/* One more message is now waiting */
|
||||
kubernetes_client->outbound_messages_waiting++;
|
||||
|
||||
/* Notify libwebsockets that we need a callback to send pending
|
||||
* messages */
|
||||
lws_callback_on_writable(kubernetes_client->wsi);
|
||||
lws_cancel_service(kubernetes_client->context);
|
||||
|
||||
}
|
||||
|
||||
/* Warn if data has to be dropped */
|
||||
else
|
||||
guac_client_log(client, GUAC_LOG_WARNING, "Send buffer could not be "
|
||||
"flushed in time to handle additional data. Outbound "
|
||||
"message dropped.");
|
||||
|
||||
pthread_mutex_unlock(&(kubernetes_client->outbound_message_lock));
|
||||
|
||||
}
|
||||
|
||||
bool guac_kubernetes_write_pending_message(guac_client* client) {
|
||||
|
||||
bool messages_remain;
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
pthread_mutex_lock(&(kubernetes_client->outbound_message_lock));
|
||||
|
||||
/* Send one message from top of buffer */
|
||||
if (kubernetes_client->outbound_messages_waiting > 0) {
|
||||
|
||||
/* Obtain pointer to message at top */
|
||||
int top = kubernetes_client->outbound_messages_top;
|
||||
guac_kubernetes_message* message =
|
||||
&(kubernetes_client->outbound_messages[top]);
|
||||
|
||||
/* Write message including channel index */
|
||||
lws_write(kubernetes_client->wsi,
|
||||
((unsigned char*) message) + LWS_PRE,
|
||||
message->length + 1, LWS_WRITE_BINARY);
|
||||
|
||||
/* Advance top to next message */
|
||||
kubernetes_client->outbound_messages_top++;
|
||||
kubernetes_client->outbound_messages_top %=
|
||||
GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES;
|
||||
|
||||
/* One less message is waiting */
|
||||
kubernetes_client->outbound_messages_waiting--;
|
||||
|
||||
}
|
||||
|
||||
/* Record whether messages remained at time of completion */
|
||||
messages_remain = (kubernetes_client->outbound_messages_waiting > 0);
|
||||
|
||||
pthread_mutex_unlock(&(kubernetes_client->outbound_message_lock));
|
||||
|
||||
return messages_remain;
|
||||
|
||||
}
|
||||
|
||||
|
144
src/protocols/kubernetes/io.h
Normal file
144
src/protocols/kubernetes/io.h
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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_IO_H
|
||||
#define GUAC_KUBERNETES_IO_H
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* The maximum amount of data to include in any particular WebSocket message
|
||||
* to Kubernetes. This excludes the storage space required for the channel
|
||||
* index.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_MAX_MESSAGE_SIZE 1024
|
||||
|
||||
/**
|
||||
* The index of the Kubernetes channel used for STDIN.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_CHANNEL_STDIN 0
|
||||
|
||||
/**
|
||||
* The index of the Kubernetes channel used for STDOUT.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_CHANNEL_STDOUT 1
|
||||
|
||||
/**
|
||||
* The index of the Kubernetes channel used for STDERR.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_CHANNEL_STDERR 2
|
||||
|
||||
/**
|
||||
* The index of the Kubernetes channel used for terminal resize messages.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_CHANNEL_RESIZE 4
|
||||
|
||||
/**
|
||||
* An outbound message to be received by Kubernetes over WebSocket.
|
||||
*/
|
||||
typedef struct guac_kubernetes_message {
|
||||
|
||||
/**
|
||||
* lws_write() requires leading padding of LWS_PRE bytes to provide
|
||||
* scratch space for WebSocket framing.
|
||||
*/
|
||||
uint8_t _padding[LWS_PRE];
|
||||
|
||||
/**
|
||||
* The index of the channel receiving the data, such as
|
||||
* GUAC_KUBERNETES_CHANNEL_STDIN.
|
||||
*/
|
||||
uint8_t channel;
|
||||
|
||||
/**
|
||||
* The data that should be sent to Kubernetes (along with the channel
|
||||
* index).
|
||||
*/
|
||||
char data[GUAC_KUBERNETES_MAX_MESSAGE_SIZE];
|
||||
|
||||
/**
|
||||
* The length of the data to be sent, excluding the channel index.
|
||||
*/
|
||||
int length;
|
||||
|
||||
} guac_kubernetes_message;
|
||||
|
||||
|
||||
/**
|
||||
* Handles data received from Kubernetes over WebSocket, decoding the channel
|
||||
* index of the received data and forwarding that data accordingly.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client associated with the connection to Kubernetes.
|
||||
*
|
||||
* @param buffer
|
||||
* The data received from Kubernetes.
|
||||
*
|
||||
* @param length
|
||||
* The size of the data received from Kubernetes, in bytes.
|
||||
*/
|
||||
void guac_kubernetes_receive_data(guac_client* client,
|
||||
const char* buffer, size_t length);
|
||||
|
||||
/**
|
||||
* Requests that the given data be sent along the given channel to the
|
||||
* Kubernetes server when the WebSocket connection is next available for
|
||||
* writing. If the WebSocket connection has not been available for writing for
|
||||
* long enough that the outbound message buffer is full, the request to send
|
||||
* this particular message will be dropped.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client associated with the Kubernetes connection.
|
||||
*
|
||||
* @param channel
|
||||
* The Kubernetes channel on which to send the message,
|
||||
* such as GUAC_KUBERNETES_CHANNEL_STDIN.
|
||||
*
|
||||
* @param data
|
||||
* A buffer containing the data to send.
|
||||
*
|
||||
* @param length
|
||||
* The number of bytes to send.
|
||||
*/
|
||||
void guac_kubernetes_send_message(guac_client* client,
|
||||
int channel, const char* data, int length);
|
||||
|
||||
/**
|
||||
* Writes the oldest pending message within the outbound message queue,
|
||||
* as scheduled with guac_kubernetes_send_message(), removing that message
|
||||
* from the queue. This function MAY NOT be invoked outside the libwebsockets
|
||||
* event callback and MUST only be invoked in the context of a
|
||||
* LWS_CALLBACK_CLIENT_WRITEABLE event. If no messages are pending, this
|
||||
* function has no effect.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client associated with the Kubernetes connection.
|
||||
*
|
||||
* @return
|
||||
* true if messages still remain to be written within the outbound message
|
||||
* queue, false otherwise.
|
||||
*/
|
||||
bool guac_kubernetes_write_pending_message(guac_client* client);
|
||||
|
||||
#endif
|
||||
|
387
src/protocols/kubernetes/kubernetes.c
Normal file
387
src/protocols/kubernetes/kubernetes.c
Normal file
@ -0,0 +1,387 @@
|
||||
/*
|
||||
* 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 "client.h"
|
||||
#include "common/recording.h"
|
||||
#include "io.h"
|
||||
#include "kubernetes.h"
|
||||
#include "ssl.h"
|
||||
#include "terminal/terminal.h"
|
||||
#include "url.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* Callback invoked by libwebsockets for events related to a WebSocket being
|
||||
* used for communicating with an attached Kubernetes pod.
|
||||
*
|
||||
* @param wsi
|
||||
* The libwebsockets handle for the WebSocket connection.
|
||||
*
|
||||
* @param reason
|
||||
* The reason (event) that this callback was invoked.
|
||||
*
|
||||
* @param user
|
||||
* Arbitrary data assocated with the WebSocket session. In some cases,
|
||||
* this is actually event-specific data (such as the
|
||||
* LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERT event).
|
||||
*
|
||||
* @param in
|
||||
* A pointer to arbitrary, reason-specific data.
|
||||
*
|
||||
* @param length
|
||||
* An arbitrary, reason-specific length value.
|
||||
*
|
||||
* @return
|
||||
* An undocumented integer value related the success of handling the
|
||||
* event, or -1 if the WebSocket connection should be closed.
|
||||
*/
|
||||
static int guac_kubernetes_lws_callback(struct lws* wsi,
|
||||
enum lws_callback_reasons reason, void* user,
|
||||
void* in, size_t length) {
|
||||
|
||||
guac_client* client = guac_kubernetes_lws_current_client;
|
||||
|
||||
/* Do not handle any further events if connection is closing */
|
||||
if (client->state != GUAC_CLIENT_RUNNING)
|
||||
return lws_callback_http_dummy(wsi, reason, user, in, length);
|
||||
|
||||
switch (reason) {
|
||||
|
||||
/* Complete initialization of SSL */
|
||||
case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS:
|
||||
guac_kubernetes_init_ssl(client, (SSL_CTX*) user);
|
||||
break;
|
||||
|
||||
/* Failed to connect */
|
||||
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND,
|
||||
"Error connecting to Kubernetes server: %s",
|
||||
in != NULL ? (char*) in : "(no error description "
|
||||
"available)");
|
||||
break;
|
||||
|
||||
/* Connected / logged in */
|
||||
case LWS_CALLBACK_CLIENT_ESTABLISHED:
|
||||
guac_client_log(client, GUAC_LOG_INFO,
|
||||
"Kubernetes connection successful.");
|
||||
|
||||
/* Schedule check for pending messages in case messages were added
|
||||
* to the outbound message buffer prior to the connection being
|
||||
* fully established */
|
||||
lws_callback_on_writable(wsi);
|
||||
break;
|
||||
|
||||
/* Data received via WebSocket */
|
||||
case LWS_CALLBACK_CLIENT_RECEIVE:
|
||||
guac_kubernetes_receive_data(client, (const char*) in, length);
|
||||
break;
|
||||
|
||||
/* WebSocket is ready for writing */
|
||||
case LWS_CALLBACK_CLIENT_WRITEABLE:
|
||||
|
||||
/* Send any pending messages, requesting another callback if
|
||||
* yet more messages remain */
|
||||
if (guac_kubernetes_write_pending_message(client))
|
||||
lws_callback_on_writable(wsi);
|
||||
break;
|
||||
|
||||
#ifdef HAVE_LWS_CALLBACK_CLIENT_CLOSED
|
||||
/* Connection closed (client-specific) */
|
||||
case LWS_CALLBACK_CLIENT_CLOSED:
|
||||
#endif
|
||||
|
||||
/* Connection closed */
|
||||
case LWS_CALLBACK_CLOSED:
|
||||
guac_client_stop(client);
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "WebSocket connection to "
|
||||
"Kubernetes server closed.");
|
||||
break;
|
||||
|
||||
/* No other event types are applicable */
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return lws_callback_http_dummy(wsi, reason, user, in, length);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all WebSocket protocols which should be declared as supported by
|
||||
* libwebsockets during the initial WebSocket handshake, along with
|
||||
* corresponding event-handling callbacks.
|
||||
*/
|
||||
struct lws_protocols guac_kubernetes_lws_protocols[] = {
|
||||
{
|
||||
.name = GUAC_KUBERNETES_LWS_PROTOCOL,
|
||||
.callback = guac_kubernetes_lws_callback
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
/**
|
||||
* Input thread, started by the main Kubernetes client thread. This thread
|
||||
* continuously reads from the terminal's STDIN and transfers all read
|
||||
* data to the Kubernetes connection.
|
||||
*
|
||||
* @param data
|
||||
* The current guac_client instance.
|
||||
*
|
||||
* @return
|
||||
* Always NULL.
|
||||
*/
|
||||
static void* guac_kubernetes_input_thread(void* data) {
|
||||
|
||||
guac_client* client = (guac_client*) data;
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
char buffer[GUAC_KUBERNETES_MAX_MESSAGE_SIZE];
|
||||
int bytes_read;
|
||||
|
||||
/* Write all data read */
|
||||
while ((bytes_read = guac_terminal_read_stdin(kubernetes_client->term, buffer, sizeof(buffer))) > 0) {
|
||||
|
||||
/* Send received data to Kubernetes along STDIN channel */
|
||||
guac_kubernetes_send_message(client, GUAC_KUBERNETES_CHANNEL_STDIN,
|
||||
buffer, bytes_read);
|
||||
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
void* guac_kubernetes_client_thread(void* data) {
|
||||
|
||||
guac_client* client = (guac_client*) data;
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->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) {
|
||||
kubernetes_client->recording = guac_common_recording_create(client,
|
||||
settings->recording_path,
|
||||
settings->recording_name,
|
||||
settings->create_recording_path,
|
||||
!settings->recording_exclude_output,
|
||||
!settings->recording_exclude_mouse,
|
||||
settings->recording_include_keys);
|
||||
}
|
||||
|
||||
/* Create terminal */
|
||||
kubernetes_client->term = guac_terminal_create(client,
|
||||
kubernetes_client->clipboard,
|
||||
settings->max_scrollback, settings->font_name, settings->font_size,
|
||||
settings->resolution, settings->width, settings->height,
|
||||
settings->color_scheme, settings->backspace);
|
||||
|
||||
/* Fail if terminal init failed */
|
||||
if (kubernetes_client->term == NULL) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Terminal initialization failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Set up typescript, if requested */
|
||||
if (settings->typescript_path != NULL) {
|
||||
guac_terminal_create_typescript(kubernetes_client->term,
|
||||
settings->typescript_path,
|
||||
settings->typescript_name,
|
||||
settings->create_typescript_path);
|
||||
}
|
||||
|
||||
/* Init libwebsockets context creation parameters */
|
||||
struct lws_context_creation_info context_info = {
|
||||
.port = CONTEXT_PORT_NO_LISTEN, /* We are not a WebSocket server */
|
||||
.uid = -1,
|
||||
.gid = -1,
|
||||
.protocols = guac_kubernetes_lws_protocols,
|
||||
.user = client
|
||||
};
|
||||
|
||||
/* Init WebSocket connection parameters which do not vary by Guacmaole
|
||||
* connection parameters or creation of future libwebsockets objects */
|
||||
struct lws_client_connect_info connection_info = {
|
||||
.host = settings->hostname,
|
||||
.address = settings->hostname,
|
||||
.origin = settings->hostname,
|
||||
.port = settings->port,
|
||||
.protocol = GUAC_KUBERNETES_LWS_PROTOCOL,
|
||||
.pwsi = &kubernetes_client->wsi,
|
||||
.userdata = client
|
||||
};
|
||||
|
||||
/* If requested, use an SSL/TLS connection for communication with
|
||||
* Kubernetes. Note that we disable hostname checks here because we
|
||||
* do our own validation - libwebsockets does not validate properly if
|
||||
* IP addresses are used. */
|
||||
if (settings->use_ssl) {
|
||||
context_info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
|
||||
connection_info.ssl_connection = LCCSCF_USE_SSL
|
||||
| LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
|
||||
}
|
||||
|
||||
/* Create libwebsockets context */
|
||||
kubernetes_client->context = lws_create_context(&context_info);
|
||||
if (!kubernetes_client->context) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Initialization of libwebsockets failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Generate path dynamically */
|
||||
connection_info.context = kubernetes_client->context;
|
||||
connection_info.path = endpoint_path;
|
||||
|
||||
/* Open WebSocket connection to Kubernetes */
|
||||
kubernetes_client->wsi = lws_client_connect_via_info(&connection_info);
|
||||
if (kubernetes_client->wsi == NULL) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Connection via libwebsockets failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Init outbound message buffer */
|
||||
pthread_mutex_init(&(kubernetes_client->outbound_message_lock), NULL);
|
||||
|
||||
/* Start input thread */
|
||||
if (pthread_create(&(input_thread), NULL, guac_kubernetes_input_thread, (void*) client)) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Force a redraw of the attached display (there will be no content
|
||||
* otherwise, given the stream nature of attaching to a running
|
||||
* container) */
|
||||
guac_kubernetes_force_redraw(client);
|
||||
|
||||
/* As long as client is connected, continue polling libwebsockets */
|
||||
while (client->state == GUAC_CLIENT_RUNNING) {
|
||||
|
||||
/* Cease polling libwebsockets if an error condition is signalled */
|
||||
if (lws_service(kubernetes_client->context,
|
||||
GUAC_KUBERNETES_SERVICE_INTERVAL) < 0)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
/* Kill client and Wait for input thread to die */
|
||||
guac_terminal_stop(kubernetes_client->term);
|
||||
guac_client_stop(client);
|
||||
pthread_join(input_thread, NULL);
|
||||
|
||||
fail:
|
||||
|
||||
/* Kill and free terminal, if allocated */
|
||||
if (kubernetes_client->term != NULL)
|
||||
guac_terminal_free(kubernetes_client->term);
|
||||
|
||||
/* Clean up recording, if in progress */
|
||||
if (kubernetes_client->recording != NULL)
|
||||
guac_common_recording_free(kubernetes_client->recording);
|
||||
|
||||
/* Free WebSocket context if successfully allocated */
|
||||
if (kubernetes_client->context != NULL)
|
||||
lws_context_destroy(kubernetes_client->context);
|
||||
|
||||
guac_client_log(client, GUAC_LOG_INFO, "Kubernetes connection ended.");
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
void guac_kubernetes_resize(guac_client* client, int rows, int columns) {
|
||||
|
||||
char buffer[64];
|
||||
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Send request only if different from last request */
|
||||
if (kubernetes_client->rows != rows ||
|
||||
kubernetes_client->columns != columns) {
|
||||
|
||||
kubernetes_client->rows = rows;
|
||||
kubernetes_client->columns = columns;
|
||||
|
||||
/* Construct terminal resize message for Kubernetes */
|
||||
int length = snprintf(buffer, sizeof(buffer),
|
||||
"{\"Width\":%i,\"Height\":%i}", columns, rows);
|
||||
|
||||
/* Schedule message for sending */
|
||||
guac_kubernetes_send_message(client, GUAC_KUBERNETES_CHANNEL_RESIZE,
|
||||
buffer, length);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void guac_kubernetes_force_redraw(guac_client* client) {
|
||||
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Get current terminal dimensions */
|
||||
guac_terminal* term = kubernetes_client->term;
|
||||
int rows = term->term_height;
|
||||
int columns = term->term_width;
|
||||
|
||||
/* Force a redraw by increasing the terminal size by one character in
|
||||
* each dimension and then resizing it back to normal (the same technique
|
||||
* used by kubectl */
|
||||
guac_kubernetes_resize(client, rows + 1, columns + 1);
|
||||
guac_kubernetes_resize(client, rows, columns);
|
||||
|
||||
}
|
||||
|
168
src/protocols/kubernetes/kubernetes.h
Normal file
168
src/protocols/kubernetes/kubernetes.h
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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_H
|
||||
#define GUAC_KUBERNETES_H
|
||||
|
||||
#include "common/clipboard.h"
|
||||
#include "common/recording.h"
|
||||
#include "io.h"
|
||||
#include "settings.h"
|
||||
#include "terminal/terminal.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
/**
|
||||
* The name of the WebSocket protocol specific to Kubernetes which should be
|
||||
* sent to the Kubernetes server when attaching to a pod.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_LWS_PROTOCOL "v4.channel.k8s.io"
|
||||
|
||||
/**
|
||||
* The maximum number of messages to allow within the outbound message buffer.
|
||||
* If messages are sent despite the buffer being full, those messages will be
|
||||
* dropped.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES 8
|
||||
|
||||
/**
|
||||
* The maximum number of milliseconds to wait for a libwebsockets event to
|
||||
* occur before entering another iteration of the libwebsockets event loop.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_SERVICE_INTERVAL 1000
|
||||
|
||||
/**
|
||||
* Kubernetes-specific client data.
|
||||
*/
|
||||
typedef struct guac_kubernetes_client {
|
||||
|
||||
/**
|
||||
* Kubernetes connection settings.
|
||||
*/
|
||||
guac_kubernetes_settings* settings;
|
||||
|
||||
/**
|
||||
* The libwebsockets context associated with the connected WebSocket.
|
||||
*/
|
||||
struct lws_context* context;
|
||||
|
||||
/**
|
||||
* The connected WebSocket.
|
||||
*/
|
||||
struct lws* wsi;
|
||||
|
||||
/**
|
||||
* Outbound message ring buffer for outbound WebSocket messages. As
|
||||
* libwebsockets uses an event loop for all operations, outbound messages
|
||||
* may be sent only in context of a particular event received via a
|
||||
* callback. Until that event is received, pending data must accumulate in
|
||||
* a buffer.
|
||||
*/
|
||||
guac_kubernetes_message outbound_messages[GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES];
|
||||
|
||||
/**
|
||||
* The number of messages currently waiting in the outbound message
|
||||
* buffer.
|
||||
*/
|
||||
int outbound_messages_waiting;
|
||||
|
||||
/**
|
||||
* The index of the oldest entry in the outbound message buffer. Newer
|
||||
* messages follow this entry.
|
||||
*/
|
||||
int outbound_messages_top;
|
||||
|
||||
/**
|
||||
* Lock which is acquired when the outbound message buffer is being read
|
||||
* or manipulated.
|
||||
*/
|
||||
pthread_mutex_t outbound_message_lock;
|
||||
|
||||
/**
|
||||
* The Kubernetes client thread.
|
||||
*/
|
||||
pthread_t client_thread;
|
||||
|
||||
/**
|
||||
* The current clipboard contents.
|
||||
*/
|
||||
guac_common_clipboard* clipboard;
|
||||
|
||||
/**
|
||||
* The terminal which will render all output from the Kubernetes pod.
|
||||
*/
|
||||
guac_terminal* term;
|
||||
|
||||
/**
|
||||
* The number of rows last sent to Kubernetes in a terminal resize
|
||||
* request.
|
||||
*/
|
||||
int rows;
|
||||
|
||||
/**
|
||||
* The number of columns last sent to Kubernetes in a terminal resize
|
||||
* request.
|
||||
*/
|
||||
int columns;
|
||||
|
||||
/**
|
||||
* The in-progress session recording, or NULL if no recording is in
|
||||
* progress.
|
||||
*/
|
||||
guac_common_recording* recording;
|
||||
|
||||
} guac_kubernetes_client;
|
||||
|
||||
/**
|
||||
* Main Kubernetes client thread, handling transfer of STDOUT/STDERR of an
|
||||
* attached Kubernetes pod to STDOUT of the terminal.
|
||||
*/
|
||||
void* guac_kubernetes_client_thread(void* data);
|
||||
|
||||
/**
|
||||
* Sends a message to the Kubernetes server requesting that the terminal be
|
||||
* resized to the given dimensions. This message may be queued until the
|
||||
* underlying WebSocket connection is ready to send.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client associated with the Kubernetes connection.
|
||||
*
|
||||
* @param rows
|
||||
* The new terminal size in rows.
|
||||
*
|
||||
* @param columns
|
||||
* The new terminal size in columns.
|
||||
*/
|
||||
void guac_kubernetes_resize(guac_client* client, int rows, int columns);
|
||||
|
||||
/**
|
||||
* Sends messages to the Kubernetes server such that the terminal is forced
|
||||
* to redraw. This function should be invoked at the beginning of each
|
||||
* session in order to restore expected display state.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client associated with the Kubernetes connection.
|
||||
*/
|
||||
void guac_kubernetes_force_redraw(guac_client* client);
|
||||
|
||||
#endif
|
||||
|
52
src/protocols/kubernetes/pipe.c
Normal file
52
src/protocols/kubernetes/pipe.c
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 "kubernetes.h"
|
||||
#include "terminal/terminal.h"
|
||||
#include "pipe.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/stream-types.h>
|
||||
#include <guacamole/socket.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
int guac_kubernetes_pipe_handler(guac_user* user, guac_stream* stream,
|
||||
char* mimetype, char* name) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Redirect STDIN if pipe has required name */
|
||||
if (strcmp(name, GUAC_KUBERNETES_STDIN_PIPE_NAME) == 0) {
|
||||
guac_terminal_send_stream(kubernetes_client->term, user, stream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* No other inbound pipe streams are supported */
|
||||
guac_protocol_send_ack(user->socket, stream, "No such input stream.",
|
||||
GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
|
||||
guac_socket_flush(user->socket);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
40
src/protocols/kubernetes/pipe.h
Normal file
40
src/protocols/kubernetes/pipe.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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_PIPE_H
|
||||
#define GUAC_KUBERNETES_PIPE_H
|
||||
|
||||
#include <guacamole/user.h>
|
||||
|
||||
/**
|
||||
* The name reserved for the inbound pipe stream which forces the terminal
|
||||
* emulator's STDIN to be received from the pipe.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_STDIN_PIPE_NAME "STDIN"
|
||||
|
||||
/**
|
||||
* Handles an incoming stream from a Guacamole "pipe" instruction. If the pipe
|
||||
* is named "STDIN", the the contents of the pipe stream are redirected to
|
||||
* STDIN of the terminal emulator for as long as the pipe is open.
|
||||
*/
|
||||
guac_user_pipe_handler guac_kubernetes_pipe_handler;
|
||||
|
||||
#endif
|
||||
|
403
src/protocols/kubernetes/settings.c
Normal file
403
src/protocols/kubernetes/settings.c
Normal file
@ -0,0 +1,403 @@
|
||||
/*
|
||||
* 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 "settings.h"
|
||||
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Client plugin arguments */
|
||||
const char* GUAC_KUBERNETES_CLIENT_ARGS[] = {
|
||||
"hostname",
|
||||
"port",
|
||||
"namespace",
|
||||
"pod",
|
||||
"container",
|
||||
"use-ssl",
|
||||
"client-cert",
|
||||
"client-key",
|
||||
"ca-cert",
|
||||
"ignore-cert",
|
||||
"font-name",
|
||||
"font-size",
|
||||
"color-scheme",
|
||||
"typescript-path",
|
||||
"typescript-name",
|
||||
"create-typescript-path",
|
||||
"recording-path",
|
||||
"recording-name",
|
||||
"recording-exclude-output",
|
||||
"recording-exclude-mouse",
|
||||
"recording-include-keys",
|
||||
"create-recording-path",
|
||||
"read-only",
|
||||
"backspace",
|
||||
"scrollback",
|
||||
NULL
|
||||
};
|
||||
|
||||
enum KUBERNETES_ARGS_IDX {
|
||||
|
||||
/**
|
||||
* The hostname to connect to. Required.
|
||||
*/
|
||||
IDX_HOSTNAME,
|
||||
|
||||
/**
|
||||
* The port to connect to. Optional.
|
||||
*/
|
||||
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,
|
||||
|
||||
/**
|
||||
* The certificate to use if performing SSL/TLS client authentication to
|
||||
* authenticate with the Kubernetes server, in PEM format. If omitted, SSL
|
||||
* client authentication will not be performed.
|
||||
*/
|
||||
IDX_CLIENT_CERT,
|
||||
|
||||
/**
|
||||
* The key to use if performing SSL/TLS client authentication to
|
||||
* authenticate with the Kubernetes server, in PEM format. If omitted, SSL
|
||||
* client authentication will not be performed.
|
||||
*/
|
||||
IDX_CLIENT_KEY,
|
||||
|
||||
/**
|
||||
* The certificate of the certificate authority that signed the certificate
|
||||
* of the Kubernetes server, in PEM format. If omitted. verification of
|
||||
* the Kubernetes server certificate will use the systemwide certificate
|
||||
* authorities.
|
||||
*/
|
||||
IDX_CA_CERT,
|
||||
|
||||
/**
|
||||
* Whether the certificate used by the Kubernetes server for SSL/TLS should
|
||||
* be ignored if it cannot be validated.
|
||||
*/
|
||||
IDX_IGNORE_CERT,
|
||||
|
||||
/**
|
||||
* The name of the font to use within the terminal.
|
||||
*/
|
||||
IDX_FONT_NAME,
|
||||
|
||||
/**
|
||||
* The size of the font to use within the terminal, in points.
|
||||
*/
|
||||
IDX_FONT_SIZE,
|
||||
|
||||
/**
|
||||
* The color scheme to use, as a series of semicolon-separated color-value
|
||||
* pairs: "background: <color>", "foreground: <color>", or
|
||||
* "color<n>: <color>", where <n> is a number from 0 to 255, and <color> is
|
||||
* "color<n>" or an X11 color code (e.g. "aqua" or "rgb:12/34/56").
|
||||
* The color scheme can also be one of the special values: "black-white",
|
||||
* "white-black", "gray-black", or "green-black".
|
||||
*/
|
||||
IDX_COLOR_SCHEME,
|
||||
|
||||
/**
|
||||
* The full absolute path to the directory in which typescripts should be
|
||||
* written.
|
||||
*/
|
||||
IDX_TYPESCRIPT_PATH,
|
||||
|
||||
/**
|
||||
* The name that should be given to typescripts which are written in the
|
||||
* given path. Each typescript will consist of two files: "NAME" and
|
||||
* "NAME.timing".
|
||||
*/
|
||||
IDX_TYPESCRIPT_NAME,
|
||||
|
||||
/**
|
||||
* Whether the specified typescript path should automatically be created
|
||||
* if it does not yet exist.
|
||||
*/
|
||||
IDX_CREATE_TYPESCRIPT_PATH,
|
||||
|
||||
/**
|
||||
* The full absolute path to the directory in which screen recordings
|
||||
* should be written.
|
||||
*/
|
||||
IDX_RECORDING_PATH,
|
||||
|
||||
/**
|
||||
* The name that should be given to screen recordings which are written in
|
||||
* the given path.
|
||||
*/
|
||||
IDX_RECORDING_NAME,
|
||||
|
||||
/**
|
||||
* Whether output which is broadcast to each connected client (graphics,
|
||||
* streams, etc.) should NOT be included in the session recording. Output
|
||||
* is included by default, as it is necessary for any recording which must
|
||||
* later be viewable as video.
|
||||
*/
|
||||
IDX_RECORDING_EXCLUDE_OUTPUT,
|
||||
|
||||
/**
|
||||
* Whether changes to mouse state, such as position and buttons pressed or
|
||||
* released, should NOT be included in the session recording. Mouse state
|
||||
* is included by default, as it is necessary for the mouse cursor to be
|
||||
* rendered in any resulting video.
|
||||
*/
|
||||
IDX_RECORDING_EXCLUDE_MOUSE,
|
||||
|
||||
/**
|
||||
* Whether keys pressed and released should be included in the session
|
||||
* recording. Key events are NOT included by default within the recording,
|
||||
* as doing so has privacy and security implications. Including key events
|
||||
* may be necessary in certain auditing contexts, but should only be done
|
||||
* with caution. Key events can easily contain sensitive information, such
|
||||
* as passwords, credit card numbers, etc.
|
||||
*/
|
||||
IDX_RECORDING_INCLUDE_KEYS,
|
||||
|
||||
/**
|
||||
* Whether the specified screen recording path should automatically be
|
||||
* created if it does not yet exist.
|
||||
*/
|
||||
IDX_CREATE_RECORDING_PATH,
|
||||
|
||||
/**
|
||||
* "true" if this connection should be read-only (user input should be
|
||||
* dropped), "false" or blank otherwise.
|
||||
*/
|
||||
IDX_READ_ONLY,
|
||||
|
||||
/**
|
||||
* ASCII code, as an integer to use for the backspace key, or 127
|
||||
* if not specified.
|
||||
*/
|
||||
IDX_BACKSPACE,
|
||||
|
||||
/**
|
||||
* The maximum size of the scrollback buffer in rows.
|
||||
*/
|
||||
IDX_SCROLLBACK,
|
||||
|
||||
KUBERNETES_ARGS_COUNT
|
||||
};
|
||||
|
||||
guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
|
||||
int argc, const char** argv) {
|
||||
|
||||
/* Validate arg count */
|
||||
if (argc != KUBERNETES_ARGS_COUNT) {
|
||||
guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection "
|
||||
"parameters provided: expected %i, got %i.",
|
||||
KUBERNETES_ARGS_COUNT, argc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
guac_kubernetes_settings* settings =
|
||||
calloc(1, sizeof(guac_kubernetes_settings));
|
||||
|
||||
/* 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,
|
||||
IDX_USE_SSL, false);
|
||||
|
||||
/* Read SSL/TLS connection details only if enabled */
|
||||
if (settings->use_ssl) {
|
||||
|
||||
settings->client_cert =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS,
|
||||
argv, IDX_CLIENT_CERT, NULL);
|
||||
|
||||
settings->client_key =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS,
|
||||
argv, IDX_CLIENT_KEY, NULL);
|
||||
|
||||
settings->ca_cert =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS,
|
||||
argv, IDX_CA_CERT, NULL);
|
||||
|
||||
settings->ignore_cert =
|
||||
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS,
|
||||
argv, IDX_IGNORE_CERT, false);
|
||||
|
||||
}
|
||||
|
||||
/* Read-only mode */
|
||||
settings->read_only =
|
||||
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_READ_ONLY, false);
|
||||
|
||||
/* Read maximum scrollback size */
|
||||
settings->max_scrollback =
|
||||
guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_SCROLLBACK, GUAC_KUBERNETES_DEFAULT_MAX_SCROLLBACK);
|
||||
|
||||
/* Read font name */
|
||||
settings->font_name =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_FONT_NAME, GUAC_KUBERNETES_DEFAULT_FONT_NAME);
|
||||
|
||||
/* Read font size */
|
||||
settings->font_size =
|
||||
guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_FONT_SIZE, GUAC_KUBERNETES_DEFAULT_FONT_SIZE);
|
||||
|
||||
/* Copy requested color scheme */
|
||||
settings->color_scheme =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_COLOR_SCHEME, "");
|
||||
|
||||
/* Pull width/height/resolution directly from user */
|
||||
settings->width = user->info.optimal_width;
|
||||
settings->height = user->info.optimal_height;
|
||||
settings->resolution = user->info.optimal_resolution;
|
||||
|
||||
/* Read typescript path */
|
||||
settings->typescript_path =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_TYPESCRIPT_PATH, NULL);
|
||||
|
||||
/* Read typescript name */
|
||||
settings->typescript_name =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_TYPESCRIPT_NAME, GUAC_KUBERNETES_DEFAULT_TYPESCRIPT_NAME);
|
||||
|
||||
/* Parse path creation flag */
|
||||
settings->create_typescript_path =
|
||||
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_CREATE_TYPESCRIPT_PATH, false);
|
||||
|
||||
/* Read recording path */
|
||||
settings->recording_path =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_RECORDING_PATH, NULL);
|
||||
|
||||
/* Read recording name */
|
||||
settings->recording_name =
|
||||
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_RECORDING_NAME, GUAC_KUBERNETES_DEFAULT_RECORDING_NAME);
|
||||
|
||||
/* Parse output exclusion flag */
|
||||
settings->recording_exclude_output =
|
||||
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_RECORDING_EXCLUDE_OUTPUT, false);
|
||||
|
||||
/* Parse mouse exclusion flag */
|
||||
settings->recording_exclude_mouse =
|
||||
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_RECORDING_EXCLUDE_MOUSE, false);
|
||||
|
||||
/* Parse key event inclusion flag */
|
||||
settings->recording_include_keys =
|
||||
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_RECORDING_INCLUDE_KEYS, false);
|
||||
|
||||
/* Parse path creation flag */
|
||||
settings->create_recording_path =
|
||||
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_CREATE_RECORDING_PATH, false);
|
||||
|
||||
/* Parse backspace key code */
|
||||
settings->backspace =
|
||||
guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
|
||||
IDX_BACKSPACE, 127);
|
||||
|
||||
/* Parsing was successful */
|
||||
return settings;
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
free(settings->client_key);
|
||||
free(settings->ca_cert);
|
||||
|
||||
/* Free display preferences */
|
||||
free(settings->font_name);
|
||||
free(settings->color_scheme);
|
||||
|
||||
/* Free typescript settings */
|
||||
free(settings->typescript_name);
|
||||
free(settings->typescript_path);
|
||||
|
||||
/* Free screen recording settings */
|
||||
free(settings->recording_name);
|
||||
free(settings->recording_path);
|
||||
|
||||
/* Free overall structure */
|
||||
free(settings);
|
||||
|
||||
}
|
||||
|
279
src/protocols/kubernetes/settings.h
Normal file
279
src/protocols/kubernetes/settings.h
Normal file
@ -0,0 +1,279 @@
|
||||
/*
|
||||
* 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_SETTINGS_H
|
||||
#define GUAC_KUBERNETES_SETTINGS_H
|
||||
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* The name of the font to use for the terminal if no name is specified.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_DEFAULT_FONT_NAME "monospace"
|
||||
|
||||
/**
|
||||
* The size of the font to use for the terminal if no font size is specified,
|
||||
* in points.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_DEFAULT_FONT_SIZE 12
|
||||
|
||||
/**
|
||||
* The port to connect to when initiating any Kubernetes connection, if no
|
||||
* other port is specified.
|
||||
*/
|
||||
#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"
|
||||
|
||||
/**
|
||||
* The filename to use for the screen recording, if not specified.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_DEFAULT_RECORDING_NAME "recording"
|
||||
|
||||
/**
|
||||
* The default maximum scrollback size in rows.
|
||||
*/
|
||||
#define GUAC_KUBERNETES_DEFAULT_MAX_SCROLLBACK 1000
|
||||
|
||||
/**
|
||||
* Settings for the Kubernetes connection. The values for this structure are
|
||||
* parsed from the arguments given during the Guacamole protocol handshake
|
||||
* using the guac_kubernetes_parse_args() function.
|
||||
*/
|
||||
typedef struct guac_kubernetes_settings {
|
||||
|
||||
/**
|
||||
* The hostname of the Kubernetes server to connect to.
|
||||
*/
|
||||
char* hostname;
|
||||
|
||||
/**
|
||||
* The port of the Kubernetes server to connect to.
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* The certificate to use if performing SSL/TLS client authentication to
|
||||
* authenticate with the Kubernetes server, in PEM format. If omitted, SSL
|
||||
* client authentication will not be performed.
|
||||
*/
|
||||
char* client_cert;
|
||||
|
||||
/**
|
||||
* The key to use if performing SSL/TLS client authentication to
|
||||
* authenticate with the Kubernetes server, in PEM format. If omitted, SSL
|
||||
* client authentication will not be performed.
|
||||
*/
|
||||
char* client_key;
|
||||
|
||||
/**
|
||||
* The certificate of the certificate authority that signed the certificate
|
||||
* of the Kubernetes server, in PEM format. If omitted. verification of
|
||||
* the Kubernetes server certificate will use the systemwide certificate
|
||||
* authorities.
|
||||
*/
|
||||
char* ca_cert;
|
||||
|
||||
/**
|
||||
* Whether the certificate used by the Kubernetes server for SSL/TLS should
|
||||
* be ignored if it cannot be validated.
|
||||
*/
|
||||
bool ignore_cert;
|
||||
|
||||
/**
|
||||
* Whether this connection is read-only, and user input should be dropped.
|
||||
*/
|
||||
bool read_only;
|
||||
|
||||
/**
|
||||
* The maximum size of the scrollback buffer in rows.
|
||||
*/
|
||||
int max_scrollback;
|
||||
|
||||
/**
|
||||
* The name of the font to use for display rendering.
|
||||
*/
|
||||
char* font_name;
|
||||
|
||||
/**
|
||||
* The size of the font to use, in points.
|
||||
*/
|
||||
int font_size;
|
||||
|
||||
/**
|
||||
* The name of the color scheme to use.
|
||||
*/
|
||||
char* color_scheme;
|
||||
|
||||
/**
|
||||
* The desired width of the terminal display, in pixels.
|
||||
*/
|
||||
int width;
|
||||
|
||||
/**
|
||||
* The desired height of the terminal display, in pixels.
|
||||
*/
|
||||
int height;
|
||||
|
||||
/**
|
||||
* The desired screen resolution, in DPI.
|
||||
*/
|
||||
int resolution;
|
||||
|
||||
/**
|
||||
* The path in which the typescript should be saved, if enabled. If no
|
||||
* typescript should be saved, this will be NULL.
|
||||
*/
|
||||
char* typescript_path;
|
||||
|
||||
/**
|
||||
* The filename to use for the typescript, if enabled.
|
||||
*/
|
||||
char* typescript_name;
|
||||
|
||||
/**
|
||||
* Whether the typescript path should be automatically created if it does
|
||||
* not already exist.
|
||||
*/
|
||||
bool create_typescript_path;
|
||||
|
||||
/**
|
||||
* The path in which the screen recording should be saved, if enabled. If
|
||||
* no screen recording should be saved, this will be NULL.
|
||||
*/
|
||||
char* recording_path;
|
||||
|
||||
/**
|
||||
* The filename to use for the screen recording, if enabled.
|
||||
*/
|
||||
char* recording_name;
|
||||
|
||||
/**
|
||||
* Whether the screen recording path should be automatically created if it
|
||||
* does not already exist.
|
||||
*/
|
||||
bool create_recording_path;
|
||||
|
||||
/**
|
||||
* Whether output which is broadcast to each connected client (graphics,
|
||||
* streams, etc.) should NOT be included in the session recording. Output
|
||||
* is included by default, as it is necessary for any recording which must
|
||||
* later be viewable as video.
|
||||
*/
|
||||
bool recording_exclude_output;
|
||||
|
||||
/**
|
||||
* Whether changes to mouse state, such as position and buttons pressed or
|
||||
* released, should NOT be included in the session recording. Mouse state
|
||||
* is included by default, as it is necessary for the mouse cursor to be
|
||||
* rendered in any resulting video.
|
||||
*/
|
||||
bool recording_exclude_mouse;
|
||||
|
||||
/**
|
||||
* Whether keys pressed and released should be included in the session
|
||||
* recording. Key events are NOT included by default within the recording,
|
||||
* as doing so has privacy and security implications. Including key events
|
||||
* may be necessary in certain auditing contexts, but should only be done
|
||||
* with caution. Key events can easily contain sensitive information, such
|
||||
* as passwords, credit card numbers, etc.
|
||||
*/
|
||||
bool recording_include_keys;
|
||||
|
||||
/**
|
||||
* The ASCII code, as an integer, that the Kubernetes client will use when
|
||||
* the backspace key is pressed. By default, this is 127, ASCII delete, if
|
||||
* not specified in the client settings.
|
||||
*/
|
||||
int backspace;
|
||||
|
||||
} guac_kubernetes_settings;
|
||||
|
||||
/**
|
||||
* Parses all given args, storing them in a newly-allocated settings object. If
|
||||
* the args fail to parse, NULL is returned.
|
||||
*
|
||||
* @param user
|
||||
* The user who submitted the given arguments while joining the
|
||||
* connection.
|
||||
*
|
||||
* @param argc
|
||||
* The number of arguments within the argv array.
|
||||
*
|
||||
* @param argv
|
||||
* The values of all arguments provided by the user.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated settings object which must be freed with
|
||||
* guac_kubernetes_settings_free() when no longer needed. If the arguments
|
||||
* fail to parse, NULL is returned.
|
||||
*/
|
||||
guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
|
||||
int argc, const char** argv);
|
||||
|
||||
/**
|
||||
* Frees the given guac_kubernetes_settings object, having been previously
|
||||
* allocated via guac_kubernetes_parse_args().
|
||||
*
|
||||
* @param settings
|
||||
* The settings object to free.
|
||||
*/
|
||||
void guac_kubernetes_settings_free(guac_kubernetes_settings* settings);
|
||||
|
||||
/**
|
||||
* NULL-terminated array of accepted client args.
|
||||
*/
|
||||
extern const char* GUAC_KUBERNETES_CLIENT_ARGS[];
|
||||
|
||||
#endif
|
||||
|
210
src/protocols/kubernetes/ssl.c
Normal file
210
src/protocols/kubernetes/ssl.c
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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 "kubernetes.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <openssl/asn1.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
|
||||
/**
|
||||
* Tests whether the given hostname is, in fact, an IP address.
|
||||
*
|
||||
* @param hostname
|
||||
* The hostname to test.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the given hostname is an IP address, zero otherwise.
|
||||
*/
|
||||
static int guac_kubernetes_is_address(const char* hostname) {
|
||||
|
||||
/* Attempt to interpret the hostname as an IP address */
|
||||
ASN1_OCTET_STRING* ip = a2i_IPADDRESS(hostname);
|
||||
|
||||
/* If unsuccessful, the hostname is not an IP address */
|
||||
if (ip == NULL)
|
||||
return 0;
|
||||
|
||||
/* Converted hostname must be freed */
|
||||
ASN1_OCTET_STRING_free(ip);
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given PEM certificate, returning a new OpenSSL X509 structure
|
||||
* representing that certificate.
|
||||
*
|
||||
* @param pem
|
||||
* The PEM certificate.
|
||||
*
|
||||
* @return
|
||||
* An X509 structure representing the given certificate, or NULL if the
|
||||
* certificate was unreadable.
|
||||
*/
|
||||
static X509* guac_kubernetes_read_cert(char* pem) {
|
||||
|
||||
/* Prepare a BIO which provides access to the in-memory CA cert */
|
||||
BIO* bio = BIO_new_mem_buf(pem, -1);
|
||||
if (bio == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Read the CA cert as PEM */
|
||||
X509* certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||
if (certificate == NULL) {
|
||||
BIO_free(bio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return certificate;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given PEM private key, returning a new OpenSSL EVP_PKEY structure
|
||||
* representing that key.
|
||||
*
|
||||
* @param pem
|
||||
* The PEM private key.
|
||||
*
|
||||
* @return
|
||||
* An EVP_KEY representing the given private key, or NULL if the private
|
||||
* key was unreadable.
|
||||
*/
|
||||
static EVP_PKEY* guac_kubernetes_read_key(char* pem) {
|
||||
|
||||
/* Prepare a BIO which provides access to the in-memory key */
|
||||
BIO* bio = BIO_new_mem_buf(pem, -1);
|
||||
if (bio == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Read the private key as PEM */
|
||||
EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
|
||||
if (key == NULL) {
|
||||
BIO_free(bio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return key;
|
||||
|
||||
}
|
||||
|
||||
void guac_kubernetes_init_ssl(guac_client* client, SSL_CTX* context) {
|
||||
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
guac_kubernetes_settings* settings = kubernetes_client->settings;
|
||||
|
||||
/* Bypass certificate checks if requested */
|
||||
if (settings->ignore_cert)
|
||||
SSL_CTX_set_verify(context, SSL_VERIFY_NONE, NULL);
|
||||
|
||||
/* Otherwise use the given CA certificate to validate (if any) */
|
||||
else if (settings->ca_cert != NULL) {
|
||||
|
||||
/* Read CA certificate from configuration data */
|
||||
X509* ca_cert = guac_kubernetes_read_cert(settings->ca_cert);
|
||||
if (ca_cert == NULL) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Provided CA certificate is unreadable");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add certificate to CA store */
|
||||
X509_STORE* ca_store = SSL_CTX_get_cert_store(context);
|
||||
if (!X509_STORE_add_cert(ca_store, ca_cert)) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Unable to add CA certificate to certificate store of "
|
||||
"SSL context");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Certificate for SSL/TLS client auth */
|
||||
if (settings->client_cert != NULL) {
|
||||
|
||||
/* Read client certificate from configuration data */
|
||||
X509* client_cert = guac_kubernetes_read_cert(settings->client_cert);
|
||||
if (client_cert == NULL) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Provided client certificate is unreadable");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Use parsed certificate for authentication */
|
||||
if (!SSL_CTX_use_certificate(context, client_cert)) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Client certificate could not be used for SSL/TLS "
|
||||
"client authentication");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Private key for SSL/TLS client auth */
|
||||
if (settings->client_key != NULL) {
|
||||
|
||||
/* Read client private key from configuration data */
|
||||
EVP_PKEY* client_key = guac_kubernetes_read_key(settings->client_key);
|
||||
if (client_key == NULL) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Provided client private key is unreadable");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Use parsed key for authentication */
|
||||
if (!SSL_CTX_use_PrivateKey(context, client_key)) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Client private key could not be used for SSL/TLS "
|
||||
"client authentication");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Enable hostname checking */
|
||||
X509_VERIFY_PARAM *param = SSL_CTX_get0_param(context);
|
||||
X509_VERIFY_PARAM_set_hostflags(param,
|
||||
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
|
||||
|
||||
/* Validate properly depending on whether hostname is an IP address */
|
||||
if (guac_kubernetes_is_address(settings->hostname)) {
|
||||
if (!X509_VERIFY_PARAM_set1_ip_asc(param, settings->hostname)) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Server IP address validation could not be enabled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!X509_VERIFY_PARAM_set1_host(param, settings->hostname, 0)) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Server hostname validation could not be enabled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
41
src/protocols/kubernetes/ssl.h
Normal file
41
src/protocols/kubernetes/ssl.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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_SSL_H
|
||||
#define GUAC_KUBERNETES_SSL_H
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
/**
|
||||
* Initializes the given SSL/TLS context using the configuration parameters
|
||||
* associated with the given guac_client, setting up hostname/address
|
||||
* validation and client authentication.
|
||||
*
|
||||
* @param client
|
||||
* The guac_client associated with the Kubernetes connection.
|
||||
*
|
||||
* @param context
|
||||
* The SSL_CTX in use by libwebsockets.
|
||||
*/
|
||||
void guac_kubernetes_init_ssl(guac_client* client, SSL_CTX* context);
|
||||
|
||||
#endif
|
||||
|
137
src/protocols/kubernetes/url.c
Normal file
137
src/protocols/kubernetes/url.c
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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 "url.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Returns whether the given character is a character that need not be
|
||||
* escaped when included as part of a component of a URL.
|
||||
*
|
||||
* @param c
|
||||
* The character to test.
|
||||
*
|
||||
* @return
|
||||
* Zero if the character does not need to be escaped when included as
|
||||
* part of a component of a URL, non-zero otherwise.
|
||||
*/
|
||||
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);
|
||||
|
||||
}
|
||||
|
85
src/protocols/kubernetes/url.h
Normal file
85
src/protocols/kubernetes/url.h
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
116
src/protocols/kubernetes/user.c
Normal file
116
src/protocols/kubernetes/user.c
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 "clipboard.h"
|
||||
#include "common/cursor.h"
|
||||
#include "input.h"
|
||||
#include "kubernetes.h"
|
||||
#include "pipe.h"
|
||||
#include "settings.h"
|
||||
#include "terminal/terminal.h"
|
||||
#include "user.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/socket.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int guac_kubernetes_user_join_handler(guac_user* user, int argc, char** argv) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) client->data;
|
||||
|
||||
/* Parse provided arguments */
|
||||
guac_kubernetes_settings* settings = guac_kubernetes_parse_args(user,
|
||||
argc, (const char**) argv);
|
||||
|
||||
/* Fail if settings cannot be parsed */
|
||||
if (settings == NULL) {
|
||||
guac_user_log(user, GUAC_LOG_INFO,
|
||||
"Badly formatted client arguments.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Store settings at user level */
|
||||
user->data = settings;
|
||||
|
||||
/* Connect to Kubernetes if owner */
|
||||
if (user->owner) {
|
||||
|
||||
/* Store owner's settings at client level */
|
||||
kubernetes_client->settings = settings;
|
||||
|
||||
/* Start client thread */
|
||||
if (pthread_create(&(kubernetes_client->client_thread), NULL,
|
||||
guac_kubernetes_client_thread, (void*) client)) {
|
||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||
"Unable to start Kubernetes client thread");
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* If not owner, synchronize with current display */
|
||||
else {
|
||||
guac_terminal_dup(kubernetes_client->term, user, user->socket);
|
||||
guac_socket_flush(user->socket);
|
||||
}
|
||||
|
||||
/* Only handle events if not read-only */
|
||||
if (!settings->read_only) {
|
||||
|
||||
/* General mouse/keyboard/clipboard events */
|
||||
user->key_handler = guac_kubernetes_user_key_handler;
|
||||
user->mouse_handler = guac_kubernetes_user_mouse_handler;
|
||||
user->clipboard_handler = guac_kubernetes_clipboard_handler;
|
||||
|
||||
/* STDIN redirection */
|
||||
user->pipe_handler = guac_kubernetes_pipe_handler;
|
||||
|
||||
/* Display size change events */
|
||||
user->size_handler = guac_kubernetes_user_size_handler;
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guac_kubernetes_user_leave_handler(guac_user* user) {
|
||||
|
||||
guac_kubernetes_client* kubernetes_client =
|
||||
(guac_kubernetes_client*) user->client->data;
|
||||
|
||||
/* Update shared cursor state */
|
||||
guac_common_cursor_remove_user(kubernetes_client->term->cursor, user);
|
||||
|
||||
/* Free settings if not owner (owner settings will be freed with client) */
|
||||
if (!user->owner) {
|
||||
guac_kubernetes_settings* settings =
|
||||
(guac_kubernetes_settings*) user->data;
|
||||
guac_kubernetes_settings_free(settings);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
36
src/protocols/kubernetes/user.h
Normal file
36
src/protocols/kubernetes/user.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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_USER_H
|
||||
#define GUAC_KUBERNETES_USER_H
|
||||
|
||||
#include <guacamole/user.h>
|
||||
|
||||
/**
|
||||
* Handler for joining users.
|
||||
*/
|
||||
guac_user_join_handler guac_kubernetes_user_join_handler;
|
||||
|
||||
/**
|
||||
* Handler for leaving users.
|
||||
*/
|
||||
guac_user_leave_handler guac_kubernetes_user_leave_handler;
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user