Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4624533
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
63 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:31 PM (7 h, 6 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1987847
Default Alt Text
(63 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment