GUACAMOLE-623: Add support for SSL.
This commit is contained in:
parent
2e50573531
commit
83a531bc89
@ -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"])
|
||||
|
||||
#
|
||||
|
@ -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@
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
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
|
||||
|
Loading…
Reference in New Issue
Block a user