Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/daemons/based/based_remote.c b/daemons/based/based_remote.c
index 42835be279..f0091701a3 100644
--- a/daemons/based/based_remote.c
+++ b/daemons/based/based_remote.c
@@ -1,667 +1,667 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/crm.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <inttypes.h> // PRIx64
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <errno.h>
#include <glib.h>
#include <libxml/tree.h>
#include <crm/common/ipc.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/xml.h>
#include <crm/common/remote_internal.h>
#include <crm/common/tls_internal.h>
#include <crm/cib/internal.h>
#include "pacemaker-based.h"
#include <gnutls/gnutls.h>
#include <pwd.h>
#include <grp.h>
#if HAVE_SECURITY_PAM_APPL_H
# include <security/pam_appl.h>
# define HAVE_PAM 1
#elif HAVE_PAM_PAM_APPL_H
# include <pam/pam_appl.h>
# define HAVE_PAM 1
#endif
static pcmk__tls_t *tls = NULL;
extern int remote_tls_fd;
extern gboolean cib_shutdown_flag;
int init_remote_listener(int port, gboolean encrypted);
void cib_remote_connection_destroy(gpointer user_data);
// @TODO This is rather short for someone to type their password
#define REMOTE_AUTH_TIMEOUT 10000
int num_clients;
static bool authenticate_user(const char *user, const char *passwd);
static int cib_remote_listen(gpointer data);
static int cib_remote_msg(gpointer data);
static void
remote_connection_destroy(gpointer user_data)
{
crm_info("No longer listening for remote connections");
return;
}
int
init_remote_listener(int port, gboolean encrypted)
{
int rc;
int *ssock = NULL;
struct sockaddr_in saddr;
int optval;
static struct mainloop_fd_callbacks remote_listen_fd_callbacks = {
.dispatch = cib_remote_listen,
.destroy = remote_connection_destroy,
};
if (port <= 0) {
/* don't start it */
return 0;
}
if (encrypted) {
- bool use_cert = pcmk__x509_enabled(true);
+ bool use_cert = pcmk__x509_enabled();
crm_notice("Starting TLS listener on port %d", port);
rc = pcmk__init_tls(&tls, true, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_ANON);
if (rc != pcmk_rc_ok) {
return -1;
}
} else {
crm_warn("Starting plain-text listener on port %d", port);
}
#ifndef HAVE_PAM
crm_warn("This build does not support remote administrators "
"because PAM support is not available");
#endif
/* create server socket */
ssock = malloc(sizeof(int));
if(ssock == NULL) {
crm_err("Listener socket allocation failed: %s", pcmk_rc_str(errno));
return -1;
}
*ssock = socket(AF_INET, SOCK_STREAM, 0);
if (*ssock == -1) {
crm_err("Listener socket creation failed: %s", pcmk_rc_str(errno));
free(ssock);
return -1;
}
/* reuse address */
optval = 1;
rc = setsockopt(*ssock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
if (rc < 0) {
crm_err("Local address reuse not allowed on listener socket: %s",
pcmk_rc_str(errno));
}
/* bind server socket */
memset(&saddr, '\0', sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(port);
if (bind(*ssock, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) {
crm_err("Cannot bind to listener socket: %s", pcmk_rc_str(errno));
close(*ssock);
free(ssock);
return -2;
}
if (listen(*ssock, 10) == -1) {
crm_err("Cannot listen on socket: %s", pcmk_rc_str(errno));
close(*ssock);
free(ssock);
return -3;
}
mainloop_add_fd("cib-remote", G_PRIORITY_DEFAULT, *ssock, ssock, &remote_listen_fd_callbacks);
crm_debug("Started listener on port %d", port);
return *ssock;
}
static int
check_group_membership(const char *usr, const char *grp)
{
int index = 0;
struct passwd *pwd = NULL;
struct group *group = NULL;
pwd = getpwnam(usr);
if (pwd == NULL) {
crm_notice("Rejecting remote client: '%s' is not a valid user", usr);
return FALSE;
}
group = getgrgid(pwd->pw_gid);
if (group != NULL && pcmk__str_eq(grp, group->gr_name, pcmk__str_none)) {
return TRUE;
}
group = getgrnam(grp);
if (group == NULL) {
crm_err("Rejecting remote client: '%s' is not a valid group", grp);
return FALSE;
}
while (TRUE) {
char *member = group->gr_mem[index++];
if (member == NULL) {
break;
} else if (pcmk__str_eq(usr, member, pcmk__str_none)) {
return TRUE;
}
}
crm_notice("Rejecting remote client: User '%s' is not a member of "
"group '%s'", usr, grp);
return FALSE;
}
static gboolean
cib_remote_auth(xmlNode * login)
{
const char *user = NULL;
const char *pass = NULL;
const char *tmp = NULL;
if (login == NULL) {
return FALSE;
}
if (!pcmk__xe_is(login, PCMK__XE_CIB_COMMAND)) {
crm_warn("Rejecting remote client: Unrecognizable message "
"(element '%s' not '" PCMK__XE_CIB_COMMAND "')", login->name);
crm_log_xml_debug(login, "bad");
return FALSE;
}
tmp = crm_element_value(login, PCMK_XA_OP);
if (!pcmk__str_eq(tmp, "authenticate", pcmk__str_casei)) {
crm_warn("Rejecting remote client: Unrecognizable message "
"(operation '%s' not 'authenticate')", tmp);
crm_log_xml_debug(login, "bad");
return FALSE;
}
user = crm_element_value(login, PCMK_XA_USER);
pass = crm_element_value(login, PCMK__XA_PASSWORD);
if (!user || !pass) {
crm_warn("Rejecting remote client: No %s given",
((user == NULL)? "username" : "password"));
crm_log_xml_debug(login, "bad");
return FALSE;
}
crm_log_xml_debug(login, "auth");
return check_group_membership(user, CRM_DAEMON_GROUP)
&& authenticate_user(user, pass);
}
static gboolean
remote_auth_timeout_cb(gpointer data)
{
pcmk__client_t *client = data;
client->remote->auth_timeout = 0;
if (pcmk_is_set(client->flags, pcmk__client_authenticated)) {
return FALSE;
}
mainloop_del_fd(client->remote->source);
crm_err("Remote client authentication timed out");
return FALSE;
}
static int
cib_remote_listen(gpointer data)
{
int csock = 0;
unsigned laddr;
struct sockaddr_storage addr;
char ipstr[INET6_ADDRSTRLEN];
int ssock = *(int *)data;
int rc;
pcmk__client_t *new_client = NULL;
static struct mainloop_fd_callbacks remote_client_fd_callbacks = {
.dispatch = cib_remote_msg,
.destroy = cib_remote_connection_destroy,
};
/* accept the connection */
laddr = sizeof(addr);
memset(&addr, 0, sizeof(addr));
csock = accept(ssock, (struct sockaddr *)&addr, &laddr);
if (csock == -1) {
crm_warn("Could not accept remote connection: %s", pcmk_rc_str(errno));
return TRUE;
}
pcmk__sockaddr2str(&addr, ipstr);
rc = pcmk__set_nonblocking(csock);
if (rc != pcmk_rc_ok) {
crm_warn("Dropping remote connection from %s because "
"it could not be set to non-blocking: %s",
ipstr, pcmk_rc_str(rc));
close(csock);
return TRUE;
}
num_clients++;
new_client = pcmk__new_unauth_client(NULL);
new_client->remote = pcmk__assert_alloc(1, sizeof(pcmk__remote_t));
if (ssock == remote_tls_fd) {
pcmk__set_client_flags(new_client, pcmk__client_tls);
/* create gnutls session for the server socket */
new_client->remote->tls_session = pcmk__new_tls_session(tls, csock);
if (new_client->remote->tls_session == NULL) {
close(csock);
return TRUE;
}
} else {
pcmk__set_client_flags(new_client, pcmk__client_tcp);
new_client->remote->tcp_socket = csock;
}
// Require the client to authenticate within this time
new_client->remote->auth_timeout = pcmk__create_timer(REMOTE_AUTH_TIMEOUT,
remote_auth_timeout_cb,
new_client);
crm_info("%s connection from %s pending authentication for client %s",
((ssock == remote_tls_fd)? "Encrypted" : "Clear-text"),
ipstr, new_client->id);
new_client->remote->source =
mainloop_add_fd("cib-remote-client", G_PRIORITY_DEFAULT, csock, new_client,
&remote_client_fd_callbacks);
return TRUE;
}
void
cib_remote_connection_destroy(gpointer user_data)
{
pcmk__client_t *client = user_data;
int csock = 0;
if (client == NULL) {
return;
}
crm_trace("Cleaning up after client %s disconnect",
pcmk__client_name(client));
num_clients--;
crm_trace("Num unfree'd clients: %d", num_clients);
switch (PCMK__CLIENT_TYPE(client)) {
case pcmk__client_tcp:
csock = client->remote->tcp_socket;
break;
case pcmk__client_tls:
if (client->remote->tls_session) {
void *sock_ptr = gnutls_transport_get_ptr(client->remote->tls_session);
csock = GPOINTER_TO_INT(sock_ptr);
if (pcmk_is_set(client->flags,
pcmk__client_tls_handshake_complete)) {
gnutls_bye(client->remote->tls_session, GNUTLS_SHUT_WR);
}
gnutls_deinit(client->remote->tls_session);
client->remote->tls_session = NULL;
}
break;
default:
crm_warn("Unknown transport for client %s "
QB_XS " flags=%#016" PRIx64,
pcmk__client_name(client), client->flags);
}
if (csock > 0) {
close(csock);
}
pcmk__free_client(client);
crm_trace("Freed the cib client");
if (cib_shutdown_flag) {
cib_shutdown(0);
}
return;
}
static void
cib_handle_remote_msg(pcmk__client_t *client, xmlNode *command)
{
if (!pcmk__xe_is(command, PCMK__XE_CIB_COMMAND)) {
crm_log_xml_trace(command, "bad");
return;
}
if (client->name == NULL) {
client->name = pcmk__str_copy(client->id);
}
/* unset dangerous options */
pcmk__xe_remove_attr(command, PCMK__XA_SRC);
pcmk__xe_remove_attr(command, PCMK__XA_CIB_HOST);
pcmk__xe_remove_attr(command, PCMK__XA_CIB_UPDATE);
crm_xml_add(command, PCMK__XA_T, PCMK__VALUE_CIB);
crm_xml_add(command, PCMK__XA_CIB_CLIENTID, client->id);
crm_xml_add(command, PCMK__XA_CIB_CLIENTNAME, client->name);
crm_xml_add(command, PCMK__XA_CIB_USER, client->user);
if (crm_element_value(command, PCMK__XA_CIB_CALLID) == NULL) {
char *call_uuid = crm_generate_uuid();
/* fix the command */
crm_xml_add(command, PCMK__XA_CIB_CALLID, call_uuid);
free(call_uuid);
}
if (crm_element_value(command, PCMK__XA_CIB_CALLOPT) == NULL) {
crm_xml_add_int(command, PCMK__XA_CIB_CALLOPT, 0);
}
crm_log_xml_trace(command, "Remote command: ");
cib_common_callback_worker(0, 0, command, client, TRUE);
}
static int
cib_remote_msg(gpointer data)
{
xmlNode *command = NULL;
pcmk__client_t *client = data;
int rc;
const char *client_name = pcmk__client_name(client);
crm_trace("Remote %s message received for client %s",
pcmk__client_type_str(PCMK__CLIENT_TYPE(client)), client_name);
if ((PCMK__CLIENT_TYPE(client) == pcmk__client_tls)
&& !pcmk_is_set(client->flags, pcmk__client_tls_handshake_complete)) {
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;
}
crm_debug("Completed TLS handshake with remote client %s", client_name);
pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete);
if (client->remote->auth_timeout) {
g_source_remove(client->remote->auth_timeout);
}
/* Now that the handshake is done, see if any client TLS certificate is
* close to its expiration date and log if so. If a TLS certificate is not
* in use, this function will just return so we don't need to check for the
* session type here.
*/
pcmk__tls_check_cert_expiration(client->remote->tls_session);
// Require the client to authenticate within this time
client->remote->auth_timeout = pcmk__create_timer(REMOTE_AUTH_TIMEOUT,
remote_auth_timeout_cb,
client);
return 0;
}
rc = pcmk__read_available_remote_data(client->remote);
switch (rc) {
case pcmk_rc_ok:
break;
case EAGAIN:
/* We haven't read the whole message yet */
return 0;
default:
/* Error */
crm_trace("Error reading from remote client: %s", pcmk_rc_str(rc));
return -1;
}
/* must pass auth before we will process anything else */
if (!pcmk_is_set(client->flags, pcmk__client_authenticated)) {
xmlNode *reg;
const char *user = NULL;
command = pcmk__remote_message_xml(client->remote);
if (cib_remote_auth(command) == FALSE) {
pcmk__xml_free(command);
return -1;
}
pcmk__set_client_flags(client, pcmk__client_authenticated);
g_source_remove(client->remote->auth_timeout);
client->remote->auth_timeout = 0;
client->name = crm_element_value_copy(command, PCMK_XA_NAME);
user = crm_element_value(command, PCMK_XA_USER);
if (user) {
client->user = pcmk__str_copy(user);
}
crm_notice("Remote connection accepted for authenticated user %s "
QB_XS " client %s",
pcmk__s(user, ""), client_name);
/* send ACK */
reg = pcmk__xe_create(NULL, PCMK__XE_CIB_RESULT);
crm_xml_add(reg, PCMK__XA_CIB_OP, CRM_OP_REGISTER);
crm_xml_add(reg, PCMK__XA_CIB_CLIENTID, client->id);
pcmk__remote_send_xml(client->remote, reg);
pcmk__xml_free(reg);
pcmk__xml_free(command);
}
command = pcmk__remote_message_xml(client->remote);
if (command != NULL) {
crm_trace("Remote message received from client %s", client_name);
cib_handle_remote_msg(client, command);
pcmk__xml_free(command);
}
return 0;
}
#ifdef HAVE_PAM
/*!
* \internal
* \brief Pass remote user's password to PAM
*
* \param[in] num_msg Number of entries in \p msg
* \param[in] msg Array of PAM messages
* \param[out] response Where to set response to PAM
* \param[in] data User data (the password string)
*
* \return PAM return code (PAM_BUF_ERR for memory errors, PAM_CONV_ERR for all
* other errors, or PAM_SUCCESS on success)
* \note See pam_conv(3) for more explanation
*/
static int
construct_pam_passwd(int num_msg, const struct pam_message **msg,
struct pam_response **response, void *data)
{
/* In theory, multiple messages are allowed, but due to OS compatibility
* issues, PAM implementations are recommended to only send one message at a
* time. We can require that here for simplicity.
*/
CRM_CHECK((num_msg == 1) && (msg != NULL) && (response != NULL)
&& (data != NULL), return PAM_CONV_ERR);
switch (msg[0]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
case PAM_PROMPT_ECHO_ON:
// Password requested
break;
case PAM_TEXT_INFO:
crm_info("PAM: %s", msg[0]->msg);
data = NULL;
break;
case PAM_ERROR_MSG:
/* In theory we should show msg[0]->msg, but that might
* contain the password, which we don't want in the logs
*/
crm_err("PAM reported an error");
data = NULL;
break;
default:
crm_warn("Ignoring PAM message of unrecognized type %d",
msg[0]->msg_style);
return PAM_CONV_ERR;
}
*response = calloc(1, sizeof(struct pam_response));
if (*response == NULL) {
return PAM_BUF_ERR;
}
(*response)->resp_retcode = 0;
(*response)->resp = pcmk__str_copy((const char *) data); // Caller will free
return PAM_SUCCESS;
}
#endif
/*!
* \internal
* \brief Verify the username and password passed for a remote CIB connection
*
* \param[in] user Username passed for remote CIB connection
* \param[in] passwd Password passed for remote CIB connection
*
* \return \c true if the username and password are accepted, otherwise \c false
* \note This function rejects all credentials when built without PAM support.
*/
static bool
authenticate_user(const char *user, const char *passwd)
{
#ifdef HAVE_PAM
int rc = 0;
bool pass = false;
const void *p_user = NULL;
struct pam_conv p_conv;
struct pam_handle *pam_h = NULL;
static const char *pam_name = NULL;
if (pam_name == NULL) {
pam_name = getenv("CIB_pam_service");
if (pam_name == NULL) {
pam_name = "login";
}
}
p_conv.conv = construct_pam_passwd;
p_conv.appdata_ptr = (void *) passwd;
rc = pam_start(pam_name, user, &p_conv, &pam_h);
if (rc != PAM_SUCCESS) {
crm_warn("Rejecting remote client for user %s "
"because PAM initialization failed: %s",
user, pam_strerror(pam_h, rc));
goto bail;
}
// Check user credentials
rc = pam_authenticate(pam_h, PAM_SILENT);
if (rc != PAM_SUCCESS) {
crm_notice("Access for remote user %s denied: %s",
user, pam_strerror(pam_h, rc));
goto bail;
}
/* Get the authenticated user name (PAM modules can map the original name to
* something else). Since the CIB manager runs as the daemon user (not
* root), that is the only user that can be successfully authenticated.
*/
rc = pam_get_item(pam_h, PAM_USER, &p_user);
if (rc != PAM_SUCCESS) {
crm_warn("Rejecting remote client for user %s "
"because PAM failed to return final user name: %s",
user, pam_strerror(pam_h, rc));
goto bail;
}
if (p_user == NULL) {
crm_warn("Rejecting remote client for user %s "
"because PAM returned no final user name", user);
goto bail;
}
// @TODO Why do we require these to match?
if (!pcmk__str_eq(p_user, user, pcmk__str_none)) {
crm_warn("Rejecting remote client for user %s "
"because PAM returned different final user name %s",
user, p_user);
goto bail;
}
// Check user account restrictions (expiration, etc.)
rc = pam_acct_mgmt(pam_h, PAM_SILENT);
if (rc != PAM_SUCCESS) {
crm_notice("Access for remote user %s denied: %s",
user, pam_strerror(pam_h, rc));
goto bail;
}
pass = true;
bail:
pam_end(pam_h, rc);
return pass;
#else
// @TODO Implement for non-PAM environments
crm_warn("Rejecting remote user %s because this build does not have "
"PAM support", user);
return false;
#endif
}
diff --git a/include/crm/common/tls_internal.h b/include/crm/common/tls_internal.h
index 68abccd5ff..747aa2d6b9 100644
--- a/include/crm/common/tls_internal.h
+++ b/include/crm/common/tls_internal.h
@@ -1,184 +1,182 @@
/*
* Copyright 2024 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.
*/
#ifndef PCMK__CRM_COMMON_TLS_INTERNAL__H
#define PCMK__CRM_COMMON_TLS_INTERNAL__H
#include <gnutls/gnutls.h> // gnutls_session_t, gnutls_dh_params_t, etc.
#include <crm/common/ipc_internal.h> // pcmk__client_t
#include <crm/common/remote_internal.h> // pcmk__remote_t
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
bool server;
gnutls_dh_params_t dh_params;
gnutls_credentials_type_t cred_type;
const char *ca_file;
const char *cert_file;
const char *crl_file;
const char *key_file;
union {
gnutls_anon_server_credentials_t anon_s;
gnutls_anon_client_credentials_t anon_c;
gnutls_certificate_credentials_t cert;
gnutls_psk_server_credentials_t psk_s;
gnutls_psk_client_credentials_t psk_c;
} credentials;
} pcmk__tls_t;
/*!
* \internal
* \brief Free a previously allocated \p pcmk__tls_t object
*
* \param[in,out] tls The object to free
*/
void pcmk__free_tls(pcmk__tls_t *tls);
/*!
* \internal
* \brief Initialize a new TLS object
*
* Unlike \p pcmk__new_tls_session, this function is used for creating the
* global environment for TLS connections.
*
* \param[in,out] tls The object to be allocated and initialized
* \param[in] server Is this a server or not?
* \param[in] cred_type What type of gnutls credentials are in use?
* (GNUTLS_CRD_* constants)
*
* \returns Standard Pacemaker return code
*/
int pcmk__init_tls(pcmk__tls_t **tls, bool server,
gnutls_credentials_type_t cred_type);
/*!
* \internal
* \brief Initialize Diffie-Hellman parameters for a TLS server
*
* \param[out] dh_params Parameter object to initialize
*
* \return Standard Pacemaker return code
* \todo The current best practice is to allow the client and server to
* negotiate the Diffie-Hellman parameters via a TLS extension (RFC 7919).
* However, we have to support both older versions of GnuTLS (<3.6) that
* don't support the extension on our side, and older Pacemaker versions
* that don't support the extension on the other side. The next best
* practice would be to use a known good prime (see RFC 5114 section 2.2),
* possibly stored in a file distributed with Pacemaker.
*/
int pcmk__init_tls_dh(gnutls_dh_params_t *dh_params);
/*!
* \internal
* \brief Initialize a new TLS session
*
* \param[in] tls A TLS environment object
* \param[in] csock Connected socket for TLS session
*
* \return Pointer to newly created session object, or NULL on error
*/
gnutls_session_t pcmk__new_tls_session(pcmk__tls_t *tls, int csock);
/*!
* \internal
* \brief Add the client PSK key to the TLS environment
*
* This function must be called for all TLS clients that are using PSK for
* authentication.
*
* \param[in,out] tls The TLS environment
* \param[in] key The client's PSK key
*/
void pcmk__tls_add_psk_key(pcmk__tls_t *tls, gnutls_datum_t *key);
/*!
* \internal
* \brief Register the server's PSK credential fetching callback
*
* This function must be called for all TLS servers that are using PSK for
* authentication.
*
* \param[in,out] tls The TLS environment
* \param[in] cb The server's PSK credential fetching callback
*/
void pcmk__tls_add_psk_callback(pcmk__tls_t *tls,
gnutls_psk_server_credentials_function *cb);
/*!
* \internal
* \brief Process handshake data from TLS client
*
* Read as much TLS handshake data as is available.
*
* \param[in] client Client connection
*
* \return Standard Pacemaker return code (of particular interest, EAGAIN
* if some data was successfully read but more data is needed)
*/
int pcmk__read_handshake_data(const pcmk__client_t *client);
/*!
* \internal
* \brief Log if a TLS certificate is near its expiration date
*
* \param[in] session The gnutls session object after handshaking is
* complete
*/
void pcmk__tls_check_cert_expiration(gnutls_session_t session);
/*!
* \internal
* \brief Perform client TLS handshake after establishing TCP socket
*
* \param[in,out] remote Newly established remote connection
* \param[in] timeout_sec Abort handshake if not completed within this time
* \param[out] gnutls_rc If this is non-NULL, it will be set to the GnuTLS
* rc (for logging) if this function returns EPROTO,
* otherwise GNUTLS_E_SUCCESS
*
* \return Standard Pacemaker return code
*/
int pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_sec,
int *gnutls_rc);
/*!
* \internal
* \brief Make a single attempt to perform the client TLS handshake
*
* \param[in,out] remote Newly established remote connection
* \param[out] gnutls_rc If this is non-NULL, it will be set to the GnuTLS
* rc (for logging) if this function returns EPROTO,
* otherwise GNUTLS_E_SUCCESS
*
* \return Standard Pacemaker return code
*/
int pcmk__tls_client_try_handshake(pcmk__remote_t *remote, int *gnutls_rc);
/*!
* \internal
* \brief Is X509 authentication supported by the environment?
*
- * \param[in] server Is this a server?
- *
* \return true if the appropriate environment variables are set (see
* etc/sysconfig/pacemaker.in), otherwise false
*/
-bool pcmk__x509_enabled(bool server);
+bool pcmk__x509_enabled(void);
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_TLS_INTERNAL__H
diff --git a/lib/cib/cib_remote.c b/lib/cib/cib_remote.c
index 70b53cea57..a482dbc409 100644
--- a/lib/cib/cib_remote.c
+++ b/lib/cib/cib_remote.c
@@ -1,673 +1,673 @@
/*
* Copyright 2008-2024 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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <netdb.h>
#include <termios.h>
#include <sys/socket.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/mainloop.h>
#include <crm/common/xml.h>
#include <crm/common/remote_internal.h>
#include <crm/common/tls_internal.h>
#include <crm/common/output_internal.h>
#include <gnutls/gnutls.h>
// GnuTLS handshake timeout in seconds
#define TLS_HANDSHAKE_TIMEOUT 5
static pcmk__tls_t *tls = NULL;
#include <arpa/inet.h>
typedef struct cib_remote_opaque_s {
int port;
char *server;
char *user;
char *passwd;
gboolean encrypted;
pcmk__remote_t command;
pcmk__remote_t callback;
pcmk__output_t *out;
time_t start_time;
int timeout_sec;
} cib_remote_opaque_t;
static int
cib_remote_perform_op(cib_t *cib, const char *op, const char *host,
const char *section, xmlNode *data,
xmlNode **output_data, int call_options,
const char *user_name)
{
int rc;
int remaining_time = 0;
time_t start_time;
xmlNode *op_msg = NULL;
xmlNode *op_reply = NULL;
cib_remote_opaque_t *private = cib->variant_opaque;
if (cib->state == cib_disconnected) {
return -ENOTCONN;
}
if (output_data != NULL) {
*output_data = NULL;
}
if (op == NULL) {
crm_err("No operation specified");
return -EINVAL;
}
rc = cib__create_op(cib, op, host, section, data, call_options, user_name,
NULL, &op_msg);
if (rc != pcmk_ok) {
return rc;
}
if (pcmk_is_set(call_options, cib_transaction)) {
rc = cib__extend_transaction(cib, op_msg);
pcmk__xml_free(op_msg);
return rc;
}
crm_trace("Sending %s message to the CIB manager", op);
if (!(call_options & cib_sync_call)) {
pcmk__remote_send_xml(&private->callback, op_msg);
} else {
pcmk__remote_send_xml(&private->command, op_msg);
}
pcmk__xml_free(op_msg);
if ((call_options & cib_discard_reply)) {
crm_trace("Discarding reply");
return pcmk_ok;
} else if (!(call_options & cib_sync_call)) {
return cib->call_id;
}
crm_trace("Waiting for a synchronous reply");
start_time = time(NULL);
remaining_time = cib->call_timeout ? cib->call_timeout : 60;
rc = pcmk_rc_ok;
while (remaining_time > 0 && (rc != ENOTCONN)) {
int reply_id = -1;
int msg_id = cib->call_id;
rc = pcmk__read_remote_message(&private->command,
remaining_time * 1000);
op_reply = pcmk__remote_message_xml(&private->command);
if (!op_reply) {
break;
}
crm_element_value_int(op_reply, PCMK__XA_CIB_CALLID, &reply_id);
if (reply_id == msg_id) {
break;
} else if (reply_id < msg_id) {
crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id);
crm_log_xml_trace(op_reply, "Old reply");
} else if ((reply_id - 10000) > msg_id) {
/* wrap-around case */
crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id);
crm_log_xml_trace(op_reply, "Old reply");
} else {
crm_err("Received a __future__ reply:" " %d (wanted %d)", reply_id, msg_id);
}
pcmk__xml_free(op_reply);
op_reply = NULL;
/* wasn't the right reply, try and read some more */
remaining_time = time(NULL) - start_time;
}
if (rc == ENOTCONN) {
crm_err("Disconnected while waiting for reply.");
return -ENOTCONN;
} else if (op_reply == NULL) {
crm_err("No reply message - empty");
return -ENOMSG;
}
crm_trace("Synchronous reply received");
/* Start processing the reply... */
if (crm_element_value_int(op_reply, PCMK__XA_CIB_RC, &rc) != 0) {
rc = -EPROTO;
}
if (rc == -pcmk_err_diff_resync) {
/* This is an internal value that clients do not and should not care about */
rc = pcmk_ok;
}
if (rc == pcmk_ok || rc == -EPERM) {
crm_log_xml_debug(op_reply, "passed");
} else {
crm_err("Call failed: %s", pcmk_strerror(rc));
crm_log_xml_warn(op_reply, "failed");
}
if (output_data == NULL) {
/* do nothing more */
} else if (!(call_options & cib_discard_reply)) {
xmlNode *wrapper = pcmk__xe_first_child(op_reply, PCMK__XE_CIB_CALLDATA,
NULL, NULL);
xmlNode *tmp = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
if (tmp == NULL) {
crm_trace("No output in reply to \"%s\" command %d", op, cib->call_id - 1);
} else {
*output_data = pcmk__xml_copy(NULL, tmp);
}
}
pcmk__xml_free(op_reply);
return rc;
}
static int
cib_remote_callback_dispatch(gpointer user_data)
{
int rc;
cib_t *cib = user_data;
cib_remote_opaque_t *private = cib->variant_opaque;
xmlNode *msg = NULL;
const char *type = NULL;
/* If start time is 0, we've previously handled a complete message and this
* connection is being reused for a new message. Reset the start_time,
* giving this new message timeout_sec from now to complete.
*/
if (private->start_time == 0) {
private->start_time = time(NULL);
}
rc = pcmk__read_available_remote_data(&private->callback);
switch (rc) {
case pcmk_rc_ok:
/* We have the whole message so process it */
break;
case EAGAIN:
/* Have we timed out? */
if (time(NULL) >= private->start_time + private->timeout_sec) {
crm_info("Error reading from CIB manager connection: %s",
pcmk_rc_str(ETIME));
return -1;
}
/* We haven't read the whole message yet */
return 0;
default:
/* Error */
crm_info("Error reading from CIB manager connection: %s",
pcmk_rc_str(rc));
return -1;
}
msg = pcmk__remote_message_xml(&private->callback);
if (msg == NULL) {
private->start_time = 0;
return 0;
}
type = crm_element_value(msg, PCMK__XA_T);
crm_trace("Activating %s callbacks...", type);
if (pcmk__str_eq(type, PCMK__VALUE_CIB, pcmk__str_none)) {
cib_native_callback(cib, msg, 0, 0);
} else if (pcmk__str_eq(type, PCMK__VALUE_CIB_NOTIFY, pcmk__str_none)) {
g_list_foreach(cib->notify_list, cib_native_notify, msg);
} else {
crm_err("Unknown message type: %s", type);
}
pcmk__xml_free(msg);
private->start_time = 0;
return 0;
}
static int
cib_remote_command_dispatch(gpointer user_data)
{
int rc;
cib_t *cib = user_data;
cib_remote_opaque_t *private = cib->variant_opaque;
/* See cib_remote_callback_dispatch */
if (private->start_time == 0) {
private->start_time = time(NULL);
}
rc = pcmk__read_available_remote_data(&private->command);
if (rc == EAGAIN) {
/* Have we timed out? */
if (time(NULL) >= private->start_time + private->timeout_sec) {
crm_info("Error reading from CIB manager connection: %s",
pcmk_rc_str(ETIME));
return -1;
}
/* We haven't read the whole message yet */
return 0;
}
free(private->command.buffer);
private->command.buffer = NULL;
crm_err("received late reply for remote cib connection, discarding");
if (rc != pcmk_rc_ok) {
crm_info("Error reading from CIB manager connection: %s",
pcmk_rc_str(rc));
return -1;
}
private->start_time = 0;
return 0;
}
static int
cib_tls_close(cib_t *cib)
{
cib_remote_opaque_t *private = cib->variant_opaque;
if (private->encrypted) {
if (private->command.tls_session) {
gnutls_bye(private->command.tls_session, GNUTLS_SHUT_RDWR);
gnutls_deinit(private->command.tls_session);
}
if (private->callback.tls_session) {
gnutls_bye(private->callback.tls_session, GNUTLS_SHUT_RDWR);
gnutls_deinit(private->callback.tls_session);
}
private->command.tls_session = NULL;
private->callback.tls_session = NULL;
pcmk__free_tls(tls);
tls = NULL;
}
if (private->command.tcp_socket) {
shutdown(private->command.tcp_socket, SHUT_RDWR); /* no more receptions */
close(private->command.tcp_socket);
}
if (private->callback.tcp_socket) {
shutdown(private->callback.tcp_socket, SHUT_RDWR); /* no more receptions */
close(private->callback.tcp_socket);
}
private->command.tcp_socket = 0;
private->callback.tcp_socket = 0;
free(private->command.buffer);
free(private->callback.buffer);
private->command.buffer = NULL;
private->callback.buffer = NULL;
return 0;
}
static void
cib_remote_connection_destroy(gpointer user_data)
{
crm_err("Connection destroyed");
cib_tls_close(user_data);
}
static int
cib_tls_signon(cib_t *cib, pcmk__remote_t *connection, gboolean event_channel)
{
cib_remote_opaque_t *private = cib->variant_opaque;
int rc;
xmlNode *answer = NULL;
xmlNode *login = NULL;
static struct mainloop_fd_callbacks cib_fd_callbacks = { 0, };
cib_fd_callbacks.dispatch =
event_channel ? cib_remote_callback_dispatch : cib_remote_command_dispatch;
cib_fd_callbacks.destroy = cib_remote_connection_destroy;
connection->tcp_socket = -1;
connection->tls_session = NULL;
rc = pcmk__connect_remote(private->server, private->port, 0, NULL,
&(connection->tcp_socket), NULL, NULL);
if (rc != pcmk_rc_ok) {
crm_info("Remote connection to %s:%d failed: %s " QB_XS " rc=%d",
private->server, private->port, pcmk_rc_str(rc), rc);
return -ENOTCONN;
}
if (private->encrypted) {
- bool use_cert = pcmk__x509_enabled(false);
+ bool use_cert = pcmk__x509_enabled();
int tls_rc = GNUTLS_E_SUCCESS;
rc = pcmk__init_tls(&tls, false, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_ANON);
if (rc != pcmk_rc_ok) {
return -1;
}
/* bind the socket to GnuTls lib */
connection->tls_session = pcmk__new_tls_session(tls, connection->tcp_socket);
if (connection->tls_session == NULL) {
cib_tls_close(cib);
return -1;
}
rc = pcmk__tls_client_handshake(connection, TLS_HANDSHAKE_TIMEOUT,
&tls_rc);
if (rc != pcmk_rc_ok) {
crm_err("Remote CIB session creation for %s:%d failed: %s",
private->server, private->port,
(rc == EPROTO)? gnutls_strerror(tls_rc) : pcmk_rc_str(rc));
gnutls_deinit(connection->tls_session);
connection->tls_session = NULL;
cib_tls_close(cib);
return -1;
}
}
/* Now that the handshake is done, see if any client TLS certificate is
* close to its expiration date and log if so. If a TLS certificate is not
* in use, this function will just return so we don't need to check for the
* session type here.
*/
pcmk__tls_check_cert_expiration(connection->tls_session);
/* login to server */
login = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
crm_xml_add(login, PCMK_XA_OP, "authenticate");
crm_xml_add(login, PCMK_XA_USER, private->user);
crm_xml_add(login, PCMK__XA_PASSWORD, private->passwd);
crm_xml_add(login, PCMK__XA_HIDDEN, PCMK__VALUE_PASSWORD);
pcmk__remote_send_xml(connection, login);
pcmk__xml_free(login);
rc = pcmk_ok;
if (pcmk__read_remote_message(connection, -1) == ENOTCONN) {
rc = -ENOTCONN;
}
answer = pcmk__remote_message_xml(connection);
crm_log_xml_trace(answer, "Reply");
if (answer == NULL) {
rc = -EPROTO;
} else {
/* grab the token */
const char *msg_type = crm_element_value(answer, PCMK__XA_CIB_OP);
const char *tmp_ticket = crm_element_value(answer,
PCMK__XA_CIB_CLIENTID);
if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) {
crm_err("Invalid registration message: %s", msg_type);
rc = -EPROTO;
} else if (tmp_ticket == NULL) {
rc = -EPROTO;
} else {
connection->token = strdup(tmp_ticket);
}
}
pcmk__xml_free(answer);
answer = NULL;
if (rc != 0) {
cib_tls_close(cib);
return rc;
}
crm_trace("remote client connection established");
private->timeout_sec = 60;
connection->source = mainloop_add_fd("cib-remote", G_PRIORITY_HIGH,
connection->tcp_socket, cib,
&cib_fd_callbacks);
return rc;
}
static int
cib_remote_signon(cib_t *cib, const char *name, enum cib_conn_type type)
{
int rc = pcmk_ok;
cib_remote_opaque_t *private = cib->variant_opaque;
xmlNode *hello = NULL;
if (name == NULL) {
name = pcmk__s(crm_system_name, "client");
}
if (private->passwd == NULL) {
if (private->out == NULL) {
/* If no pcmk__output_t is set, just assume that a text prompt
* is good enough.
*/
pcmk__text_prompt("Password", false, &(private->passwd));
} else {
private->out->prompt("Password", false, &(private->passwd));
}
}
if (private->server == NULL || private->user == NULL) {
rc = -EINVAL;
goto done;
}
rc = cib_tls_signon(cib, &(private->command), FALSE);
if (rc != pcmk_ok) {
goto done;
}
rc = cib_tls_signon(cib, &(private->callback), TRUE);
if (rc != pcmk_ok) {
goto done;
}
rc = cib__create_op(cib, CRM_OP_REGISTER, NULL, NULL, NULL, cib_none, NULL,
name, &hello);
if (rc != pcmk_ok) {
goto done;
}
rc = pcmk__remote_send_xml(&private->command, hello);
rc = pcmk_rc2legacy(rc);
pcmk__xml_free(hello);
done:
if (rc == pcmk_ok) {
crm_info("Opened connection to %s:%d for %s",
private->server, private->port, name);
cib->state = cib_connected_command;
cib->type = cib_command;
} else {
crm_info("Connection to %s:%d for %s failed: %s\n",
private->server, private->port, name, pcmk_strerror(rc));
}
return rc;
}
static int
cib_remote_signoff(cib_t *cib)
{
int rc = pcmk_ok;
crm_debug("Disconnecting from the CIB manager");
cib_tls_close(cib);
cib->cmds->end_transaction(cib, false, cib_none);
cib->state = cib_disconnected;
cib->type = cib_no_connection;
return rc;
}
static int
cib_remote_free(cib_t *cib)
{
int rc = pcmk_ok;
crm_warn("Freeing CIB");
if (cib->state != cib_disconnected) {
rc = cib_remote_signoff(cib);
if (rc == pcmk_ok) {
cib_remote_opaque_t *private = cib->variant_opaque;
free(private->server);
free(private->user);
free(private->passwd);
free(cib->cmds);
free(cib->user);
free(private);
free(cib);
}
}
return rc;
}
static int
cib_remote_register_notification(cib_t * cib, const char *callback, int enabled)
{
xmlNode *notify_msg = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
cib_remote_opaque_t *private = cib->variant_opaque;
crm_xml_add(notify_msg, PCMK__XA_CIB_OP, PCMK__VALUE_CIB_NOTIFY);
crm_xml_add(notify_msg, PCMK__XA_CIB_NOTIFY_TYPE, callback);
crm_xml_add_int(notify_msg, PCMK__XA_CIB_NOTIFY_ACTIVATE, enabled);
pcmk__remote_send_xml(&private->callback, notify_msg);
pcmk__xml_free(notify_msg);
return pcmk_ok;
}
static int
cib_remote_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data))
{
return -EPROTONOSUPPORT;
}
/*!
* \internal
* \brief Get the given CIB connection's unique client identifiers
*
* These can be used to check whether this client requested the action that
* triggered a CIB notification.
*
* \param[in] cib CIB connection
* \param[out] async_id If not \p NULL, where to store asynchronous client ID
* \param[out] sync_id If not \p NULL, where to store synchronous client ID
*
* \return Legacy Pacemaker return code (specifically, \p pcmk_ok)
*
* \note This is the \p cib_remote variant implementation of
* \p cib_api_operations_t:client_id().
* \note The client IDs are assigned during CIB sign-on.
*/
static int
cib_remote_client_id(const cib_t *cib, const char **async_id,
const char **sync_id)
{
cib_remote_opaque_t *private = cib->variant_opaque;
if (async_id != NULL) {
// private->callback is the channel for async requests
*async_id = private->callback.token;
}
if (sync_id != NULL) {
// private->command is the channel for sync requests
*sync_id = private->command.token;
}
return pcmk_ok;
}
cib_t *
cib_remote_new(const char *server, const char *user, const char *passwd, int port,
gboolean encrypted)
{
cib_remote_opaque_t *private = NULL;
cib_t *cib = cib_new_variant();
if (cib == NULL) {
return NULL;
}
private = calloc(1, sizeof(cib_remote_opaque_t));
if (private == NULL) {
free(cib);
return NULL;
}
cib->variant = cib_remote;
cib->variant_opaque = private;
private->server = pcmk__str_copy(server);
private->user = pcmk__str_copy(user);
private->passwd = pcmk__str_copy(passwd);
private->port = port;
private->encrypted = encrypted;
/* assign variant specific ops */
cib->delegate_fn = cib_remote_perform_op;
cib->cmds->signon = cib_remote_signon;
cib->cmds->signoff = cib_remote_signoff;
cib->cmds->free = cib_remote_free;
cib->cmds->register_notification = cib_remote_register_notification;
cib->cmds->set_connection_dnotify = cib_remote_set_connection_dnotify;
cib->cmds->client_id = cib_remote_client_id;
return cib;
}
void
cib__set_output(cib_t *cib, pcmk__output_t *out)
{
cib_remote_opaque_t *private;
if (cib->variant != cib_remote) {
return;
}
private = cib->variant_opaque;
private->out = out;
}
diff --git a/lib/common/tls.c b/lib/common/tls.c
index c3d99129aa..b1804c5983 100644
--- a/lib/common/tls.c
+++ b/lib/common/tls.c
@@ -1,529 +1,536 @@
/*
* Copyright 2024 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 <errno.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <stdlib.h>
#include <crm/common/tls_internal.h>
static char *
get_gnutls_priorities(gnutls_credentials_type_t cred_type)
{
const char *prio_base = pcmk__env_option(PCMK__ENV_TLS_PRIORITIES);
if (prio_base == NULL) {
prio_base = PCMK__GNUTLS_PRIORITIES;
}
return crm_strdup_printf("%s:%s", prio_base,
(cred_type == GNUTLS_CRD_ANON)? "+ANON-DH" : "+DHE-PSK:+PSK");
}
static const char *
tls_cred_str(gnutls_credentials_type_t cred_type)
{
if (cred_type == GNUTLS_CRD_ANON) {
return "unauthenticated";
} else if (cred_type == GNUTLS_CRD_PSK) {
return "shared-key-authenticated";
} else if (cred_type == GNUTLS_CRD_CERTIFICATE) {
return "certificate-authenticated";
} else {
return "unknown";
}
}
static int
tls_load_x509_data(pcmk__tls_t *tls)
{
int rc;
CRM_CHECK(tls->cred_type == GNUTLS_CRD_CERTIFICATE, return EINVAL);
/* Load a trusted CA to be used to verify client certificates. Use
* of this function instead of gnutls_certificate_set_x509_system_trust
* means we do not look at the system-wide authorities installed in
* /etc/pki somewhere. This requires the cluster admin to set up their
* own CA.
*/
rc = gnutls_certificate_set_x509_trust_file(tls->credentials.cert,
tls->ca_file,
GNUTLS_X509_FMT_PEM);
if (rc <= 0) {
crm_err("Failed to set X509 CA file: %s", gnutls_strerror(rc));
return ENODATA;
}
/* If a Certificate Revocation List (CRL) file was given in the environment,
* load that now so we know which clients have been banned.
*/
if (tls->crl_file != NULL) {
rc = gnutls_certificate_set_x509_crl_file(tls->credentials.cert,
tls->crl_file,
GNUTLS_X509_FMT_PEM);
if (rc < 0) {
crm_err("Failed to set X509 CRL file: %s",
gnutls_strerror(rc));
return ENODATA;
}
}
/* NULL = no password for the key, GNUTLS_PKCS_PLAIN = unencrypted key
* file
*/
rc = gnutls_certificate_set_x509_key_file2(tls->credentials.cert,
tls->cert_file, tls->key_file,
GNUTLS_X509_FMT_PEM, NULL,
GNUTLS_PKCS_PLAIN);
if (rc < 0) {
crm_err("Failed to set X509 cert/key pair: %s",
gnutls_strerror(rc));
return ENODATA;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Verify a peer's certificate
*
* \return 0 if the certificate is trusted and the gnutls handshake should
* continue, -1 otherwise
*/
static int
verify_peer_cert(gnutls_session_t session)
{
int rc;
int type;
unsigned int status;
gnutls_datum_t out;
/* NULL = no hostname comparison will be performed */
rc = gnutls_certificate_verify_peers3(session, NULL, &status);
/* Success means it was able to perform the verification. We still have
* to check status to see whether the cert is valid or not.
*/
if (rc != GNUTLS_E_SUCCESS) {
crm_err("Failed to verify peer certificate: %s", gnutls_strerror(rc));
return -1;
}
if (status == 0) {
/* The certificate is trusted. */
return 0;
}
type = gnutls_certificate_type_get(session);
gnutls_certificate_verification_status_print(status, type, &out, 0);
crm_err("Peer certificate invalid: %s", out.data);
gnutls_free(out.data);
return GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR;
}
static void
_gnutls_log_func(int level, const char *msg)
{
crm_trace("%s", msg);
}
void
pcmk__free_tls(pcmk__tls_t *tls)
{
if (tls == NULL) {
return;
}
/* This is only set on the server side. */
if (tls->server) {
gnutls_dh_params_deinit(tls->dh_params);
}
if (tls->cred_type == GNUTLS_CRD_ANON) {
if (tls->server) {
gnutls_anon_free_server_credentials(tls->credentials.anon_s);
} else {
gnutls_anon_free_client_credentials(tls->credentials.anon_c);
}
} else if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
gnutls_certificate_free_credentials(tls->credentials.cert);
} else if (tls->cred_type == GNUTLS_CRD_PSK) {
if (tls->server) {
gnutls_psk_free_server_credentials(tls->credentials.psk_s);
} else {
gnutls_psk_free_client_credentials(tls->credentials.psk_c);
}
}
free(tls);
tls = NULL;
gnutls_global_deinit();
}
int
pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_type)
{
int rc = pcmk_rc_ok;
if (*tls != NULL) {
return rc;
}
*tls = pcmk__assert_alloc(1, sizeof(pcmk__tls_t));
signal(SIGPIPE, SIG_IGN);
/* gnutls_global_init is safe to call multiple times, but we have to call
* gnutls_global_deinit the same number of times for that function to do
* anything.
*
* FIXME: When we can use gnutls >= 3.3.0, we don't have to call
* gnutls_global_init anymore.
*/
gnutls_global_init();
gnutls_global_set_log_level(8);
gnutls_global_set_log_function(_gnutls_log_func);
if (server) {
rc = pcmk__init_tls_dh(&(*tls)->dh_params);
if (rc != pcmk_rc_ok) {
pcmk__free_tls(*tls);
return rc;
}
}
(*tls)->cred_type = cred_type;
(*tls)->server = server;
if (cred_type == GNUTLS_CRD_ANON) {
if (server) {
gnutls_anon_allocate_server_credentials(&(*tls)->credentials.anon_s);
gnutls_anon_set_server_dh_params((*tls)->credentials.anon_s,
(*tls)->dh_params);
} else {
gnutls_anon_allocate_client_credentials(&(*tls)->credentials.anon_c);
}
} else if (cred_type == GNUTLS_CRD_CERTIFICATE) {
- /* Grab these environment variables before doing anything else. */
- if (server) {
- (*tls)->ca_file = pcmk__env_option(PCMK__ENV_CA_FILE);
- (*tls)->cert_file = pcmk__env_option(PCMK__ENV_CERT_FILE);
- (*tls)->crl_file = pcmk__env_option(PCMK__ENV_CRL_FILE);
- (*tls)->key_file = pcmk__env_option(PCMK__ENV_KEY_FILE);
- } else {
+ /* Try the PCMK_ version of each environment variable first, and if
+ * it's not set then try the CIB_ version.
+ */
+ (*tls)->ca_file = pcmk__env_option(PCMK__ENV_CA_FILE);
+ if (pcmk__str_empty((*tls)->ca_file)) {
(*tls)->ca_file = getenv("CIB_ca_file");
+ }
+
+ (*tls)->cert_file = pcmk__env_option(PCMK__ENV_CERT_FILE);
+ if (pcmk__str_empty((*tls)->cert_file)) {
(*tls)->cert_file = getenv("CIB_cert_file");
+ }
+
+ (*tls)->crl_file = pcmk__env_option(PCMK__ENV_CRL_FILE);
+ if (pcmk__str_empty((*tls)->crl_file)) {
(*tls)->crl_file = getenv("CIB_crl_file");
+ }
+
+ (*tls)->key_file = pcmk__env_option(PCMK__ENV_KEY_FILE);
+ if (pcmk__str_empty((*tls)->key_file)) {
(*tls)->key_file = getenv("CIB_key_file");
}
gnutls_certificate_allocate_credentials(&(*tls)->credentials.cert);
if (server) {
gnutls_certificate_set_dh_params((*tls)->credentials.cert,
(*tls)->dh_params);
}
rc = tls_load_x509_data(*tls);
if (rc != pcmk_rc_ok) {
pcmk__free_tls(*tls);
return rc;
}
} else if (cred_type == GNUTLS_CRD_PSK) {
if (server) {
gnutls_psk_allocate_server_credentials(&(*tls)->credentials.psk_s);
gnutls_psk_set_server_dh_params((*tls)->credentials.psk_s,
(*tls)->dh_params);
} else {
gnutls_psk_allocate_client_credentials(&(*tls)->credentials.psk_c);
}
}
return rc;
}
int
pcmk__init_tls_dh(gnutls_dh_params_t *dh_params)
{
int rc = GNUTLS_E_SUCCESS;
unsigned int dh_bits = 0;
int dh_max_bits = 0;
rc = gnutls_dh_params_init(dh_params);
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH,
GNUTLS_SEC_PARAM_NORMAL);
if (dh_bits == 0) {
rc = GNUTLS_E_DH_PRIME_UNACCEPTABLE;
goto error;
}
pcmk__scan_min_int(pcmk__env_option(PCMK__ENV_DH_MAX_BITS), &dh_max_bits, 0);
if ((dh_max_bits > 0) && (dh_bits > dh_max_bits)) {
dh_bits = dh_max_bits;
}
crm_info("Generating Diffie-Hellman parameters with %u-bit prime for TLS",
dh_bits);
rc = gnutls_dh_params_generate2(*dh_params, dh_bits);
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
return pcmk_rc_ok;
error:
crm_err("Could not initialize Diffie-Hellman parameters for TLS: %s "
QB_XS " rc=%d", gnutls_strerror(rc), rc);
return EPROTO;
}
gnutls_session_t
pcmk__new_tls_session(pcmk__tls_t *tls, int csock)
{
unsigned int conn_type = tls->server ? GNUTLS_SERVER : GNUTLS_CLIENT;
int rc = GNUTLS_E_SUCCESS;
char *prio = NULL;
gnutls_session_t session = NULL;
rc = gnutls_init(&session, conn_type);
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
/* Determine list of acceptable ciphers, etc. Pacemaker always adds the
* values required for its functionality.
*
* For an example of anonymous authentication, see:
* http://www.manpagez.com/info/gnutls/gnutls-2.10.4/gnutls_81.php#Echo-Server-with-anonymous-authentication
*/
prio = get_gnutls_priorities(tls->cred_type);
/* @TODO On the server side, it would be more efficient to cache the
* priority with gnutls_priority_init2() and set it with
* gnutls_priority_set() for all sessions.
*/
rc = gnutls_priority_set_direct(session, prio, NULL);
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
gnutls_transport_set_ptr(session,
(gnutls_transport_ptr_t) GINT_TO_POINTER(csock));
/* gnutls does not make this easy */
if (tls->cred_type == GNUTLS_CRD_ANON && tls->server) {
rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.anon_s);
} else if (tls->cred_type == GNUTLS_CRD_ANON) {
rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.anon_c);
} else if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.cert);
} else if (tls->cred_type == GNUTLS_CRD_PSK && tls->server) {
rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.psk_s);
} else if (tls->cred_type == GNUTLS_CRD_PSK) {
rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.psk_c);
} else {
crm_err("Unknown credential type: %d", tls->cred_type);
rc = EINVAL;
goto error;
}
if (rc != GNUTLS_E_SUCCESS) {
goto error;
}
free(prio);
if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
if (conn_type == GNUTLS_SERVER) {
/* Require the client to send a certificate for the server to verify. */
gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUIRE);
}
/* Register a function to verify the peer's certificate.
*
* FIXME: When we can require gnutls >= 3.4.6, remove verify_peer_cert
* and use gnutls_session_set_verify_cert instead.
*/
gnutls_certificate_set_verify_function(tls->credentials.cert, verify_peer_cert);
}
return session;
error:
crm_err("Could not initialize %s TLS %s session: %s " QB_XS " rc=%d priority='%s'",
tls_cred_str(tls->cred_type),
(conn_type == GNUTLS_SERVER)? "server" : "client",
gnutls_strerror(rc), rc, prio);
free(prio);
if (session != NULL) {
gnutls_deinit(session);
}
return NULL;
}
int
pcmk__read_handshake_data(const pcmk__client_t *client)
{
int rc = 0;
pcmk__assert((client != NULL) && (client->remote != NULL)
&& (client->remote->tls_session != NULL));
do {
rc = gnutls_handshake(client->remote->tls_session);
} while (rc == GNUTLS_E_INTERRUPTED);
if (rc == GNUTLS_E_AGAIN) {
/* No more data is available at the moment. This function should be
* invoked again once the client sends more.
*/
return EAGAIN;
} else if (rc != GNUTLS_E_SUCCESS) {
crm_err("TLS handshake with remote client failed: %s "
QB_XS " rc=%d", gnutls_strerror(rc), rc);
return EPROTO;
}
return pcmk_rc_ok;
}
void
pcmk__tls_add_psk_key(pcmk__tls_t *tls, gnutls_datum_t *key)
{
gnutls_psk_set_client_credentials(tls->credentials.psk_c,
DEFAULT_REMOTE_USERNAME, key,
GNUTLS_PSK_KEY_RAW);
}
void
pcmk__tls_add_psk_callback(pcmk__tls_t *tls,
gnutls_psk_server_credentials_function *cb)
{
gnutls_psk_set_server_credentials_function(tls->credentials.psk_s, cb);
}
void
pcmk__tls_check_cert_expiration(gnutls_session_t session)
{
gnutls_x509_crt_t cert;
const gnutls_datum_t *datum = NULL;
time_t expiry;
if (session == NULL) {
return;
}
if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
return;
}
datum = gnutls_certificate_get_ours(session);
if (datum == NULL) {
return;
}
gnutls_x509_crt_init(&cert);
gnutls_x509_crt_import(cert, datum, GNUTLS_X509_FMT_DER);
expiry = gnutls_x509_crt_get_expiration_time(cert);
if (expiry != -1) {
time_t now = time(NULL);
/* If the cert is going to expire within ~ one month (30 days), log it */
if (expiry - now <= 60 * 60 * 24 * 30) {
crm_time_t *expiry_t = pcmk__copy_timet(expiry);
crm_time_log(LOG_WARNING, "TLS certificate will expire on",
expiry_t, crm_time_log_date | crm_time_log_timeofday);
crm_time_free(expiry_t);
}
}
gnutls_x509_crt_deinit(cert);
}
int
pcmk__tls_client_try_handshake(pcmk__remote_t *remote, int *gnutls_rc)
{
int rc = pcmk_rc_ok;
if (gnutls_rc != NULL) {
*gnutls_rc = GNUTLS_E_SUCCESS;
}
rc = gnutls_handshake(remote->tls_session);
switch (rc) {
case GNUTLS_E_SUCCESS:
rc = pcmk_rc_ok;
break;
case GNUTLS_E_INTERRUPTED:
case GNUTLS_E_AGAIN:
rc = EAGAIN;
break;
default:
if (gnutls_rc != NULL) {
*gnutls_rc = rc;
}
rc = EPROTO;
break;
}
return rc;
}
int
pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_sec,
int *gnutls_rc)
{
const time_t time_limit = time(NULL) + timeout_sec;
do {
int rc = pcmk__tls_client_try_handshake(remote, gnutls_rc);
if (rc != EAGAIN) {
return rc;
}
} while (time(NULL) < time_limit);
return ETIME;
}
bool
-pcmk__x509_enabled(bool server)
+pcmk__x509_enabled(void)
{
/* Environment variables for servers come through the sysconfig file, and
* have names like PCMK_<whatever>. Environment variables for clients come
* from the environment and have names like CIB_<whatever>. This function
* is used for both, so we need to check both.
*/
- if (server) {
- return !pcmk__str_empty(pcmk__env_option(PCMK__ENV_CERT_FILE)) &&
- !pcmk__str_empty(pcmk__env_option(PCMK__ENV_CA_FILE)) &&
- !pcmk__str_empty(pcmk__env_option(PCMK__ENV_KEY_FILE));
- } else {
- return !pcmk__str_empty(getenv("CIB_cert_file")) &&
- !pcmk__str_empty(getenv("CIB_ca_file")) &&
- !pcmk__str_empty(getenv("CIB_key_file"));
- }
+ return (!pcmk__str_empty(pcmk__env_option(PCMK__ENV_CERT_FILE)) ||
+ !pcmk__str_empty(getenv("CIB_cert_file"))) &&
+ (!pcmk__str_empty(pcmk__env_option(PCMK__ENV_CA_FILE)) ||
+ !pcmk__str_empty(getenv("CIB_ca_file"))) &&
+ (!pcmk__str_empty(pcmk__env_option(PCMK__ENV_KEY_FILE)) ||
+ !pcmk__str_empty(getenv("CIB_key_file")));
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:31 PM (11 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1987847
Default Alt Text
(63 KB)

Event Timeline