GUACAMOLE-623: Add support for SSL.

This commit is contained in:
Michael Jumper 2018-09-11 03:03:17 -07:00
parent 2e50573531
commit 83a531bc89
9 changed files with 326 additions and 70 deletions

View File

@ -1225,6 +1225,7 @@ AC_ARG_ENABLE([kubernetes],
AM_CONDITIONAL([ENABLE_KUBERNETES], [test "x${enable_kubernetes}" = "xyes" \
-a "x${have_libwebsockets}" = "xyes" \
-a "x${have_ssl}" = "xyes" \
-a "x${have_terminal}" = "xyes"])
#

View File

@ -29,6 +29,7 @@ libguac_client_kubernetes_la_SOURCES = \
io.c \
pipe.c \
settings.c \
ssl.c \
kubernetes.c \
url.c \
user.c
@ -40,6 +41,7 @@ noinst_HEADERS = \
io.h \
pipe.h \
settings.h \
ssl.h \
kubernetes.h \
url.h \
user.h
@ -57,5 +59,6 @@ libguac_client_kubernetes_la_LIBADD = \
libguac_client_kubernetes_la_LDFLAGS = \
-version-info 0:0:0 \
@PTHREAD_LIBS@ \
@SSL_LIBS@ \
@WEBSOCKETS_LIBS@

View File

@ -32,12 +32,7 @@
#include <stdlib.h>
#include <string.h>
/**
* Static reference to the guac_client associated with the active Kubernetes
* connection. As guacd guarantees that each main client connection is
* isolated within its own process, this is safe.
*/
static guac_client* guac_kubernetes_lws_log_client = NULL;
guac_client* guac_kubernetes_lws_current_client = NULL;
/**
* Logging callback invoked by libwebsockets to log a single line of logging
@ -53,15 +48,18 @@ static guac_client* guac_kubernetes_lws_log_client = NULL;
* The line of logging output to log.
*/
static void guac_kubernetes_log(int level, const char* line) {
if (guac_kubernetes_lws_log_client != NULL)
guac_client_log(guac_kubernetes_lws_log_client, GUAC_LOG_DEBUG,
if (guac_kubernetes_lws_current_client != NULL)
guac_client_log(guac_kubernetes_lws_current_client, GUAC_LOG_DEBUG,
"libwebsockets: %s", line);
}
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 */
guac_kubernetes_lws_log_client = client;
lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO,
guac_kubernetes_log);

View File

@ -27,6 +27,13 @@
*/
#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.

View File

@ -18,9 +18,11 @@
*/
#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"
@ -43,8 +45,9 @@
* The reason (event) that this callback was invoked.
*
* @param user
* Arbitrary data assocated with the WebSocket session. This will always
* be a pointer to the guac_client instance.
* 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.
@ -60,14 +63,19 @@ static int guac_kubernetes_lws_callback(struct lws* wsi,
enum lws_callback_reasons reason, void* user,
void* in, size_t length) {
/* Request connection closure if client is stopped (note that the user
* pointer passed by libwebsockets may be NULL for some events) */
guac_client* client = (guac_client*) user;
if (client != NULL && client->state != GUAC_CLIENT_RUNNING)
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,
@ -256,29 +264,13 @@ void* guac_kubernetes_client_thread(void* data) {
};
/* If requested, use an SSL/TLS connection for communication with
* Kubernetes */
* 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) {
/* Enable use of SSL/TLS */
context_info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
connection_info.ssl_connection = LCCSCF_USE_SSL;
/* Bypass certificate checks if requested */
if (settings->ignore_cert) {
connection_info.ssl_connection |=
LCCSCF_ALLOW_SELFSIGNED
| LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK
| LCCSCF_ALLOW_EXPIRED;
}
/* Otherwise use the given CA certificate to validate (if any) */
else
context_info.client_ssl_ca_filepath = settings->ca_cert_file;
/* Certificate and key file for SSL/TLS client auth */
context_info.client_ssl_cert_filepath = settings->client_cert_file;
context_info.client_ssl_private_key_filepath = settings->client_key_file;
connection_info.ssl_connection = LCCSCF_USE_SSL
| LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
}
/* Create libwebsockets context */

View File

@ -31,9 +31,9 @@ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = {
"pod",
"container",
"use-ssl",
"client-cert-file",
"client-key-file",
"ca-cert-file",
"client-cert",
"client-key",
"ca-cert",
"ignore-cert",
"font-name",
"font-size",
@ -89,24 +89,26 @@ enum KUBERNETES_ARGS_IDX {
IDX_USE_SSL,
/**
* The filename of the certificate to use if performing SSL/TLS client
* authentication to authenticate with the Kubernetes server. If omitted,
* SSL client authentication will not be performed.
* 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_FILE,
IDX_CLIENT_CERT,
/**
* The filename of the key to use if performing SSL/TLS client
* authentication to authenticate with the Kubernetes server. If omitted,
* SSL client authentication will not be performed.
* 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_FILE,
IDX_CLIENT_KEY,
/**
* The filename of the certificate of the certificate authority that signed
* the certificate of the Kubernetes server.
* 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_FILE,
IDX_CA_CERT,
/**
* Whether the certificate used by the Kubernetes server for SSL/TLS should
@ -264,17 +266,17 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
/* Read SSL/TLS connection details only if enabled */
if (settings->use_ssl) {
settings->client_cert_file =
settings->client_cert =
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS,
argv, IDX_CLIENT_CERT_FILE, NULL);
argv, IDX_CLIENT_CERT, NULL);
settings->client_key_file =
settings->client_key =
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS,
argv, IDX_CLIENT_KEY_FILE, NULL);
argv, IDX_CLIENT_KEY, NULL);
settings->ca_cert_file =
settings->ca_cert =
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS,
argv, IDX_CA_CERT_FILE, NULL);
argv, IDX_CA_CERT, NULL);
settings->ignore_cert =
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS,
@ -378,9 +380,9 @@ void guac_kubernetes_settings_free(guac_kubernetes_settings* settings) {
free(settings->kubernetes_container);
/* Free SSL/TLS details */
free(settings->client_cert_file);
free(settings->client_key_file);
free(settings->ca_cert_file);
free(settings->client_cert);
free(settings->client_key);
free(settings->ca_cert);
/* Free display preferences */
free(settings->font_name);

View File

@ -103,24 +103,26 @@ typedef struct guac_kubernetes_settings {
bool use_ssl;
/**
* The filename of the certificate to use if performing SSL/TLS client
* authentication to authenticate with the Kubernetes server. If omitted,
* SSL client authentication will not be performed.
* 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_file;
char* client_cert;
/**
* The filename of the key to use if performing SSL/TLS client
* authentication to authenticate with the Kubernetes server. If omitted,
* SSL client authentication will not be performed.
* 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_file;
char* client_key;
/**
* The filename of the certificate of the certificate authority that signed
* the certificate of the Kubernetes server.
* 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_file;
char* ca_cert;
/**
* Whether the certificate used by the Kubernetes server for SSL/TLS should

View 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;
}
}
}

View 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