Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add support for authentication via TLS certs to remote CIB operations #3738

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 75 additions & 11 deletions daemons/based/based_remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "pacemaker-based.h"

#include <gnutls/gnutls.h>
#include <gnutls/x509.h>

#include <pwd.h>
#include <grp.h>
Expand All @@ -55,11 +56,7 @@ void cib_remote_connection_destroy(gpointer user_data);

gnutls_dh_params_t dh_params;
gnutls_anon_server_credentials_t anon_cred_s;
static void
debug_log(int level, const char *str)
{
fputs(str, stderr);
}
gnutls_certificate_credentials_t cert_cred_s;

// @TODO This is rather short for someone to type their password
#define REMOTE_AUTH_TIMEOUT 10000
Expand Down Expand Up @@ -97,13 +94,61 @@ init_remote_listener(int port, gboolean encrypted)
if (encrypted) {
crm_notice("Starting TLS listener on port %d", port);
crm_gnutls_global_init();
/* gnutls_global_set_log_level (10); */
gnutls_global_set_log_function(debug_log);

if (pcmk__init_tls_dh(&dh_params) != pcmk_rc_ok) {
return -1;
}
gnutls_anon_allocate_server_credentials(&anon_cred_s);
gnutls_anon_set_server_dh_params(anon_cred_s, dh_params);

if (pcmk__x509_enabled()) {
const char *ca_file = pcmk__env_option(PCMK__ENV_CA_FILE);
const char *cert_file = pcmk__env_option(PCMK__ENV_CERT_FILE);
const char *crl_file = pcmk__env_option(PCMK__ENV_CRL_FILE);
const char *key_file = pcmk__env_option(PCMK__ENV_KEY_FILE);

gnutls_certificate_allocate_credentials(&cert_cred_s);

/* 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(cert_cred_s, ca_file,
GNUTLS_X509_FMT_PEM);
if (rc <= 0) {
crm_err("Failed to set X509 CA file: %s", gnutls_strerror(rc));
return -1;
}

if (crl_file != NULL) {
rc = gnutls_certificate_set_x509_crl_file(cert_cred_s, crl_file,
GNUTLS_X509_FMT_PEM);
if (rc < 0) {
crm_err("Failed to set X509 CRL file: %s",
gnutls_strerror(rc));
return -1;
}
}

/* NULL = no password for the key, GNUTLS_PKCS_PLAIN = unencrypted key
* file
*/
rc = gnutls_certificate_set_x509_key_file2(cert_cred_s, cert_file,
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 -1;
}

gnutls_certificate_set_dh_params(cert_cred_s, dh_params);

} else {
gnutls_anon_allocate_server_credentials(&anon_cred_s);
gnutls_anon_set_server_dh_params(anon_cred_s, dh_params);
}

} else {
crm_warn("Starting plain-text listener on port %d", port);
}
Expand Down Expand Up @@ -299,13 +344,24 @@ cib_remote_listen(gpointer data)
new_client->remote = pcmk__assert_alloc(1, sizeof(pcmk__remote_t));

if (ssock == remote_tls_fd) {
gnutls_credentials_type_t cred_type;
void *credentials = NULL;

if (pcmk__x509_enabled()) {
cred_type = GNUTLS_CRD_CERTIFICATE;
credentials = cert_cred_s;
} else {
cred_type = GNUTLS_CRD_ANON;
credentials = anon_cred_s;
}

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(csock,
GNUTLS_SERVER,
GNUTLS_CRD_ANON,
anon_cred_s);
cred_type,
credentials);
if (new_client->remote->tls_session == NULL) {
close(csock);
return TRUE;
Expand Down Expand Up @@ -449,6 +505,14 @@ cib_remote_msg(gpointer data)

crm_debug("Completed TLS handshake with remote client %s", client_name);
pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete);

/* 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);

if (client->remote->auth_timeout) {
g_source_remove(client->remote->auth_timeout);
}
Expand Down
38 changes: 35 additions & 3 deletions doc/sphinx/Pacemaker_Administration/configuring.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,30 @@ the cluster.
For security reasons, this capability is disabled by default. If you wish to
allow remote access, set the ``remote-tls-port`` (encrypted) or
``remote-clear-port`` (unencrypted) CIB properties (attributes of the ``cib``
element). Encrypted communication is keyless, which makes it subject to
man-in-the-middle attacks, so either option should be used only on protected
networks.
element). Encrypted communication can be performed keyless (which makes it
subject to man-in-the-middle attacks), but a better option is to also use
TLS certificates.

To enable TLS certificates, it is recommended to first set up your own
Certificate Authority (CA) and generate a root CA certificate. Then create a
public/private key pair and certificate signing request (CSR) for your server.
Use the CA to sign this CSR.

Then, create a public/private key pair and CSR for each remote system that you
wish to have remote access. Use the CA to sign the CSRs. It is recommended to
use a unique certificate for each remote system so they can be revoked if
necessary.

The server's public/private key pair and signed certificate should be installed
to the ``/etc/pacemaker`` directory and owned by ``CIB_user``. Remember that
private keys should not be readable by anyone other than their owner. Finally,
edit the ``/etc/sysconfig/pacemaker`` file to refer to these credentials:

.. code-block:: none

PCMK_ca_file="/etc/pacemaker/ca.cert.pem"
PCMK_cert_file="/etc/pacemaker/server.cert.pem"
PCMK_key_file="/etc/pacemaker/server.key.pem"

The administrator's machine simply needs Pacemaker installed. To connect to the
cluster, set the following environment variables:
Expand All @@ -204,6 +225,14 @@ cluster, set the following environment variables:

Only the Pacemaker daemon user (|CRM_DAEMON_USER|) may be used as ``CIB_user``.

To use TLS certificates, the administrator's machine also needs their
public/private key pair, signed client certificate, and root CA certificate.
Those must additionally be specified with the following environment variables:

* :ref:`PCMK_ca_file <file>`
* :ref:`PCMK_cert_file <file>`
* :ref:`PCMK_key_file <file>`

As an example, if **node1** is a cluster node, and the CIB is configured with
``remote-tls-port`` set to 1234, the administrator could read the current
cluster configuration using the following commands, and would be prompted for
Expand All @@ -212,6 +241,9 @@ the daemon user's password:
.. code-block:: none

# export CIB_server=node1; export CIB_port=1234; export CIB_encrypted=true
# export PCMK_ca_file=/etc/pacemaker/ca.cert.pem
# export PCMK_cert_file=/etc/pacemaker/admin.cert.pem
# export PCMK_key_file=/etc/pacemaker/admin.key.pem
# cibadmin -Q

.. note::
Expand Down
35 changes: 35 additions & 0 deletions etc/sysconfig/pacemaker.in
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,41 @@
# Default: PCMK_panic_action="reboot"


## X509 Authentication

# PCMK_ca_file
#
# The location of a file containing trusted Certificate Authorities, used to
# verify client or server certificates. This file should be in PEM format.
# If set, along with PCMK_key_file and PCMK_cert_file, X509 authentication
# will be enabled for remote CIB connections.
#
# Default: PCMK_ca_file=""

# PCMK_cert_file
#
# The location of a file containing the certificate list for the matching
# PCMK_key_file, in PEM format. If set, along with PCMK_ca_file and PCMK_key_file,
# X509 authentication will be enabled for remote CIB connections.
#
# Default: PCMK_cert_file=""

# PCMK_crl_file
#
# The location of a Certificate Revocation List file, in PEM format. This
# setting is optional for X509 authentication.
#
# Default: PCMK_crl_file=""

# PCMK_key_file
#
# The location of a file containing the private key for the matching PCMK_cert_file,
# in PEM format. If set, along with PCMK_ca_file and PCMK_cert_file, X509
# authentication will be enabled for remote CIB connections.
#
# Default: PCMK_key_file=""


## Pacemaker Remote

# PCMK_authkey_location
Expand Down
4 changes: 4 additions & 0 deletions include/crm/common/options_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,17 @@ bool pcmk__valid_stonith_watchdog_timeout(const char *value);
// Constants for environment variable names
#define PCMK__ENV_AUTHKEY_LOCATION "authkey_location"
#define PCMK__ENV_BLACKBOX "blackbox"
#define PCMK__ENV_CA_FILE "ca_file"
#define PCMK__ENV_CALLGRIND_ENABLED "callgrind_enabled"
#define PCMK__ENV_CERT_FILE "cert_file"
#define PCMK__ENV_CLUSTER_TYPE "cluster_type"
#define PCMK__ENV_CRL_FILE "crl_file"
#define PCMK__ENV_DEBUG "debug"
#define PCMK__ENV_DH_MAX_BITS "dh_max_bits"
#define PCMK__ENV_FAIL_FAST "fail_fast"
#define PCMK__ENV_IPC_BUFFER "ipc_buffer"
#define PCMK__ENV_IPC_TYPE "ipc_type"
#define PCMK__ENV_KEY_FILE "key_file"
#define PCMK__ENV_LOGFACILITY "logfacility"
#define PCMK__ENV_LOGFILE "logfile"
#define PCMK__ENV_LOGFILE_MODE "logfile_mode"
Expand Down
18 changes: 18 additions & 0 deletions include/crm/common/remote_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,24 @@ int pcmk__tls_client_try_handshake(pcmk__remote_t *remote, int *gnutls_rc);
int pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_sec,
int *gnutls_rc);

/*!
* \internal
* \brief Is X509 authentication supported by the environment?
*
* \return true if the appropriate environment variables are set (see
* etc/sysconfig/pacemaker.in), otherwise false
*/
bool pcmk__x509_enabled(void);

/*!
* \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);

#ifdef __cplusplus
}
#endif
Expand Down
54 changes: 28 additions & 26 deletions lib/cib/cib_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,10 @@ cib_t *
cib_new(void)
{
const char *value = getenv("CIB_shadow");
const char *server = NULL;
const char *user = NULL;
const char *pass = NULL;
gboolean encrypted = TRUE;
int port;

if (!pcmk__str_empty(value)) {
Expand All @@ -574,37 +578,35 @@ cib_new(void)
}

value = getenv("CIB_port");
if (!pcmk__str_empty(value)) {
gboolean encrypted = TRUE;
const char *server = getenv("CIB_server");
const char *user = getenv("CIB_user");
const char *pass = getenv("CIB_passwd");

/* We don't ensure port is valid (>= 0) because cib_new() currently
* can't return NULL in practice, and introducing a NULL return here
* could cause core dumps that would previously just cause signon()
* failures.
*/
pcmk__scan_port(value, &port);

if (!crm_is_true(getenv("CIB_encrypted"))) {
encrypted = FALSE;
}
if (pcmk__str_empty(value)) {
return cib_native_new();
}

if (pcmk__str_empty(user)) {
user = CRM_DAEMON_USER;
}
/* We don't ensure port is valid (>= 0) because cib_new() currently can't
* return NULL in practice, and introducing a NULL return here could cause
* core dumps that would previously just cause signon() failures.
*/
pcmk__scan_port(value, &port);

if (pcmk__str_empty(server)) {
server = "localhost";
}
if (!crm_is_true(getenv("CIB_encrypted"))) {
encrypted = FALSE;
}

server = getenv("CIB_server");
user = getenv("CIB_user");
pass = getenv("CIB_passwd");

if (pcmk__str_empty(user)) {
user = CRM_DAEMON_USER;
}

crm_debug("Initializing %s remote CIB access to %s:%d as user %s",
(encrypted? "encrypted" : "plain-text"), server, port, user);
return cib_remote_new(server, user, pass, port, encrypted);
if (pcmk__str_empty(server)) {
server = "localhost";
}

return cib_native_new();
crm_debug("Initializing %s remote CIB access to %s:%d as user %s",
(encrypted? "encrypted" : "plain-text"), server, port, user);
return cib_remote_new(server, user, pass, port, encrypted);
}

/*!
Expand Down
Loading