diff options
Diffstat (limited to 'daemons/execd/remoted_tls.c')
-rw-r--r-- | daemons/execd/remoted_tls.c | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/daemons/execd/remoted_tls.c b/daemons/execd/remoted_tls.c new file mode 100644 index 0000000..c65e3f3 --- /dev/null +++ b/daemons/execd/remoted_tls.c @@ -0,0 +1,428 @@ +/* + * Copyright 2012-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <glib.h> +#include <unistd.h> + +#include <crm/crm.h> +#include <crm/msg_xml.h> +#include <crm/crm.h> +#include <crm/msg_xml.h> +#include <crm/common/mainloop.h> +#include <crm/common/remote_internal.h> +#include <crm/lrmd_internal.h> + +#include <netdb.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <arpa/inet.h> + +#include "pacemaker-execd.h" + +#ifdef HAVE_GNUTLS_GNUTLS_H + +# include <gnutls/gnutls.h> + +# define LRMD_REMOTE_AUTH_TIMEOUT 10000 +gnutls_psk_server_credentials_t psk_cred_s; +gnutls_dh_params_t dh_params; +static int ssock = -1; +extern int lrmd_call_id; + +static void +debug_log(int level, const char *str) +{ + fputs(str, stderr); +} + +/*! + * \internal + * \brief Read (more) TLS handshake data from client + * + * \param[in,out] client IPC client doing handshake + * + * \return 0 on success or more data needed, -1 on error + */ +static int +remoted__read_handshake_data(pcmk__client_t *client) +{ + int rc = pcmk__read_handshake_data(client); + + if (rc == EAGAIN) { + /* No more data is available at the moment. Just return for now; + * we'll get invoked again once the client sends more. + */ + return 0; + } else if (rc != pcmk_rc_ok) { + return -1; + } + + if (client->remote->auth_timeout) { + g_source_remove(client->remote->auth_timeout); + } + client->remote->auth_timeout = 0; + + pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete); + crm_notice("Remote client connection accepted"); + + /* Only a client with access to the TLS key can connect, so we can treat + * it as privileged. + */ + pcmk__set_client_flags(client, pcmk__client_privileged); + + // Alert other clients of the new connection + notify_of_new_client(client); + return 0; +} + +static int +lrmd_remote_client_msg(gpointer data) +{ + int id = 0; + int rc; + xmlNode *request = NULL; + pcmk__client_t *client = data; + + if (!pcmk_is_set(client->flags, + pcmk__client_tls_handshake_complete)) { + return remoted__read_handshake_data(client); + } + + switch (pcmk__remote_ready(client->remote, 0)) { + case pcmk_rc_ok: + break; + case ETIME: // No message available to read + return 0; + default: // Error + crm_info("Remote client disconnected while polling it"); + return -1; + } + + rc = pcmk__read_remote_message(client->remote, -1); + + request = pcmk__remote_message_xml(client->remote); + while (request) { + crm_element_value_int(request, F_LRMD_REMOTE_MSG_ID, &id); + crm_trace("Processing remote client request %d", id); + if (!client->name) { + const char *value = crm_element_value(request, F_LRMD_CLIENTNAME); + + if (value) { + client->name = strdup(value); + } + } + + lrmd_call_id++; + if (lrmd_call_id < 1) { + lrmd_call_id = 1; + } + + crm_xml_add(request, F_LRMD_CLIENTID, client->id); + crm_xml_add(request, F_LRMD_CLIENTNAME, client->name); + crm_xml_add_int(request, F_LRMD_CALLID, lrmd_call_id); + + process_lrmd_message(client, id, request); + free_xml(request); + + /* process all the messages in the current buffer */ + request = pcmk__remote_message_xml(client->remote); + } + + if (rc == ENOTCONN) { + crm_info("Remote client disconnected while reading from it"); + return -1; + } + + return 0; +} + +static void +lrmd_remote_client_destroy(gpointer user_data) +{ + pcmk__client_t *client = user_data; + + if (client == NULL) { + return; + } + + crm_notice("Cleaning up after remote client %s disconnected", + pcmk__client_name(client)); + + ipc_proxy_remove_provider(client); + + /* if this is the last remote connection, stop recurring + * operations */ + if (pcmk__ipc_client_count() == 1) { + client_disconnect_cleanup(NULL); + } + + if (client->remote->tls_session) { + void *sock_ptr; + int csock; + + sock_ptr = gnutls_transport_get_ptr(*client->remote->tls_session); + csock = GPOINTER_TO_INT(sock_ptr); + + gnutls_bye(*client->remote->tls_session, GNUTLS_SHUT_RDWR); + gnutls_deinit(*client->remote->tls_session); + gnutls_free(client->remote->tls_session); + close(csock); + } + + lrmd_client_destroy(client); + return; +} + +static gboolean +lrmd_auth_timeout_cb(gpointer data) +{ + pcmk__client_t *client = data; + + client->remote->auth_timeout = 0; + + if (pcmk_is_set(client->flags, + pcmk__client_tls_handshake_complete)) { + return FALSE; + } + + mainloop_del_fd(client->remote->source); + client->remote->source = NULL; + crm_err("Remote client authentication timed out"); + + return FALSE; +} + +// Dispatch callback for remote server socket +static int +lrmd_remote_listen(gpointer data) +{ + int csock = -1; + gnutls_session_t *session = NULL; + pcmk__client_t *new_client = NULL; + + // For client socket + static struct mainloop_fd_callbacks lrmd_remote_fd_cb = { + .dispatch = lrmd_remote_client_msg, + .destroy = lrmd_remote_client_destroy, + }; + + CRM_CHECK(ssock >= 0, return TRUE); + + if (pcmk__accept_remote_connection(ssock, &csock) != pcmk_rc_ok) { + return TRUE; + } + + session = pcmk__new_tls_session(csock, GNUTLS_SERVER, GNUTLS_CRD_PSK, + psk_cred_s); + if (session == NULL) { + close(csock); + return TRUE; + } + + new_client = pcmk__new_unauth_client(NULL); + new_client->remote = calloc(1, sizeof(pcmk__remote_t)); + pcmk__set_client_flags(new_client, pcmk__client_tls); + new_client->remote->tls_session = session; + + // Require the client to authenticate within this time + new_client->remote->auth_timeout = g_timeout_add(LRMD_REMOTE_AUTH_TIMEOUT, + lrmd_auth_timeout_cb, + new_client); + crm_info("Remote client pending authentication " + CRM_XS " %p id: %s", new_client, new_client->id); + + new_client->remote->source = + mainloop_add_fd("pacemaker-remote-client", G_PRIORITY_DEFAULT, csock, + new_client, &lrmd_remote_fd_cb); + return TRUE; +} + +static void +tls_server_dropped(gpointer user_data) +{ + crm_notice("TLS server session ended"); + return; +} + +// \return 0 on success, -1 on error (gnutls_psk_server_credentials_function) +static int +lrmd_tls_server_key_cb(gnutls_session_t session, const char *username, gnutls_datum_t * key) +{ + return (lrmd__init_remote_key(key) == pcmk_rc_ok)? 0 : -1; +} + +static int +bind_and_listen(struct addrinfo *addr) +{ + int optval; + int fd; + int rc; + char buffer[INET6_ADDRSTRLEN] = { 0, }; + + pcmk__sockaddr2str(addr->ai_addr, buffer); + crm_trace("Attempting to bind to address %s", buffer); + + fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (fd < 0) { + crm_perror(LOG_ERR, "Listener socket creation failed"); + return -1; + } + + /* reuse address */ + optval = 1; + rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); + if (rc < 0) { + crm_perror(LOG_ERR, "Local address reuse not allowed on %s", buffer); + close(fd); + return -1; + } + + if (addr->ai_family == AF_INET6) { + optval = 0; + rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &optval, sizeof(optval)); + if (rc < 0) { + crm_perror(LOG_INFO, "Couldn't disable IPV6-only on %s", buffer); + close(fd); + return -1; + } + } + + if (bind(fd, addr->ai_addr, addr->ai_addrlen) != 0) { + crm_perror(LOG_ERR, "Cannot bind to %s", buffer); + close(fd); + return -1; + } + + if (listen(fd, 10) == -1) { + crm_perror(LOG_ERR, "Cannot listen on %s", buffer); + close(fd); + return -1; + } + return fd; +} + +static int +get_address_info(const char *bind_name, int port, struct addrinfo **res) +{ + int rc; + char port_str[6]; // at most "65535" + struct addrinfo hints; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_UNSPEC; // IPv6 or IPv4 + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + snprintf(port_str, sizeof(port_str), "%d", port); + rc = getaddrinfo(bind_name, port_str, &hints, res); + if (rc) { + crm_err("Unable to get IP address(es) for %s: %s", + (bind_name? bind_name : "local node"), gai_strerror(rc)); + return -EADDRNOTAVAIL; + } + return pcmk_ok; +} + +int +lrmd_init_remote_tls_server(void) +{ + int filter; + int port = crm_default_remote_port(); + struct addrinfo *res = NULL, *iter; + gnutls_datum_t psk_key = { NULL, 0 }; + const char *bind_name = getenv("PCMK_remote_address"); + + static struct mainloop_fd_callbacks remote_listen_fd_callbacks = { + .dispatch = lrmd_remote_listen, + .destroy = tls_server_dropped, + }; + + CRM_CHECK(ssock == -1, return ssock); + + crm_debug("Starting TLS listener on %s port %d", + (bind_name? bind_name : "all addresses on"), port); + crm_gnutls_global_init(); + gnutls_global_set_log_function(debug_log); + + if (pcmk__init_tls_dh(&dh_params) != pcmk_rc_ok) { + return -1; + } + gnutls_psk_allocate_server_credentials(&psk_cred_s); + gnutls_psk_set_server_credentials_function(psk_cred_s, lrmd_tls_server_key_cb); + gnutls_psk_set_server_dh_params(psk_cred_s, dh_params); + + /* The key callback won't get called until the first client connection + * attempt. Do it once here, so we can warn the user at start-up if we can't + * read the key. We don't error out, though, because it's fine if the key is + * going to be added later. + */ + if (lrmd__init_remote_key(&psk_key) != pcmk_rc_ok) { + crm_warn("A cluster connection will not be possible until the key is available"); + } + gnutls_free(psk_key.data); + + if (get_address_info(bind_name, port, &res) != pcmk_ok) { + return -1; + } + + /* Currently we listen on only one address from the resulting list (the + * first IPv6 address we can bind to if possible, otherwise the first IPv4 + * address we can bind to). When bind_name is NULL, this should be the + * respective wildcard address. + * + * @TODO If there is demand for specifying more than one address, allow + * bind_name to be a space-separated list, call getaddrinfo() for each, + * and create a socket for each result (set IPV6_V6ONLY on IPv6 sockets + * since IPv4 listeners will have their own sockets). + */ + iter = res; + filter = AF_INET6; + while (iter) { + if (iter->ai_family == filter) { + ssock = bind_and_listen(iter); + } + if (ssock != -1) { + break; + } + + iter = iter->ai_next; + if (iter == NULL && filter == AF_INET6) { + iter = res; + filter = AF_INET; + } + } + + if (ssock >= 0) { + mainloop_add_fd("pacemaker-remote-server", G_PRIORITY_DEFAULT, ssock, + NULL, &remote_listen_fd_callbacks); + crm_debug("Started TLS listener on %s port %d", + (bind_name? bind_name : "all addresses on"), port); + } + freeaddrinfo(res); + return ssock; +} + +void +execd_stop_tls_server(void) +{ + if (psk_cred_s) { + gnutls_psk_free_server_credentials(psk_cred_s); + psk_cred_s = 0; + } + + if (ssock >= 0) { + close(ssock); + ssock = -1; + } +} +#endif |