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

Add TLS support for Pacemaker Remote connections #3759

Merged
merged 8 commits into from
Dec 10, 2024
2 changes: 1 addition & 1 deletion daemons/based/based_remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ init_remote_listener(int port, gboolean encrypted)
}

if (encrypted) {
bool use_cert = pcmk__x509_enabled(true);
bool use_cert = pcmk__x509_enabled();

crm_notice("Starting TLS listener on port %d", port);

Expand Down
34 changes: 23 additions & 11 deletions daemons/execd/remoted_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ remoted__read_handshake_data(pcmk__client_t *client)
pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete);
crm_notice("Remote client connection accepted");

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

/* Only a client with access to the TLS key can connect, so we can treat
* it as privileged.
*/
Expand Down Expand Up @@ -346,8 +353,8 @@ lrmd_init_remote_tls_server(void)
int filter;
int port = crm_default_remote_port();
struct addrinfo *res = NULL, *iter;
gnutls_datum_t psk_key = { NULL, 0 };
const char *bind_name = pcmk__env_option(PCMK__ENV_REMOTE_ADDRESS);
bool use_cert = pcmk__x509_enabled();

static struct mainloop_fd_callbacks remote_listen_fd_callbacks = {
.dispatch = lrmd_remote_listen,
Expand All @@ -359,22 +366,27 @@ lrmd_init_remote_tls_server(void)
crm_debug("Starting TLS listener on %s port %d",
(bind_name? bind_name : "all addresses on"), port);

rc = pcmk__init_tls(&tls, true, GNUTLS_CRD_PSK);
rc = pcmk__init_tls(&tls, true, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_PSK);
if (rc != pcmk_rc_ok) {
return -1;
}

pcmk__tls_add_psk_callback(tls, lrmd_tls_server_key_cb);
if (!use_cert) {
gnutls_datum_t psk_key = { NULL, 0 };

/* The key callback won't get called until the first client connection
* attempt. Do it once here, so we can warn the user at start-up if we can't
* read the key. We don't error out, though, because it's fine if the key is
* going to be added later.
*/
if (lrmd__init_remote_key(&psk_key) != pcmk_rc_ok) {
crm_warn("A cluster connection will not be possible until the key is available");
pcmk__tls_add_psk_callback(tls, lrmd_tls_server_key_cb);

/* The key callback won't get called until the first client connection
* attempt. Do it once here, so we can warn the user at start-up if we can't
* read the key. We don't error out, though, because it's fine if the key is
* going to be added later.
*/
if (lrmd__init_remote_key(&psk_key) != pcmk_rc_ok) {
crm_warn("A cluster connection will not be possible until the key is available");
}

gnutls_free(psk_key.data);
}
gnutls_free(psk_key.data);

if (get_address_info(bind_name, port, &res) != pcmk_rc_ok) {
return -1;
Expand Down
100 changes: 85 additions & 15 deletions doc/sphinx/Pacemaker_Explained/local-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -482,21 +482,6 @@ whose location varies by OS (most commonly ``/etc/sysconfig/pacemaker`` or
sync values are more likely to preserve log messages, but with the risk
that the host may be left active if the synchronization hangs.

* - .. _pcmk_authkey_location:

.. index::
pair: node option; PCMK_authkey_location

PCMK_authkey_location
- :ref:`text <text>`
- |PCMK_AUTHKEY_FILE|
- Use the contents of this file as the authorization key to use with
:ref:`Pacemaker Remote <pacemaker_remote>` connections. This file must
be readable by Pacemaker daemons (that is, it must allow read
permissions to either the |CRM_DAEMON_USER| user or the
|CRM_DAEMON_GROUP| group), and its contents must be identical on all
nodes.

* - .. _pcmk_remote_address:

.. index::
Expand Down Expand Up @@ -526,6 +511,91 @@ whose location varies by OS (most commonly ``/etc/sysconfig/pacemaker`` or
- Use this TCP port number for :ref:`Pacemaker Remote <pacemaker_remote>`
node connections. This value must be the same on all nodes.

* - .. _pcmk_ca_file:

.. index::
pair: node option; PCMK_ca_file

PCMK_ca_file
- :ref:`text <text>`
-
- The location of a file containing trusted Certificate Authorities, used to
verify client or server certificates. This file must be in PEM format and
must be readable by Pacemaker daemons (that is, it must allow read permissions
to either the |CRM_DAEMON_USER| user or the |CRM_DAEMON_GROUP| group).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? The controller and executor both run as root, and I don't think any other daemons need access

If set, along with :ref:`PCMK_key_file <PCMK_key_file>` and
:ref:`PCMK_cert_file <PCMK_cert_file>`, X509 authentication will be enabled
for :ref:`Pacemaker Remote <pacemaker_remote>` and remote CIB connections.

Example: ``PCMK_ca_file="/etc/pacemaker/ca.cert.pem"``

* - .. _pcmk_cert_file:

.. index::
pair: node option; PCMK_cert_file

PCMK_cert_file
- :ref:`text <text>`
-
- The location of a file containing the signed certificate for the server
side of the connection. This file must be in PEM format and must be
readable by Pacemaker daemons (that is, it must allow read permissions
to either the |CRM_DAEMON_USER| user or the |CRM_DAEMON_GROUP| group).
If set, along with :ref:`PCMK_ca_file <PCMK_ca_file>` and
:ref:`PCMK_key_file <PCMK_key_file>`, X509 authentication will be enabled
for :ref:`Pacemaker Remote <pacemaker_remote>` and remote CIB connections.

Example: ``PCMK_cert_file="/etc/pacemaker/server.cert.pem"``

* - .. _pcmk_crl_file:

.. index::
pair: node option; PCMK_crl_file

PCMK_crl_file
- :ref:`text <text>`
-
- The location of a Certificate Revocation List file, in PEM format. This
setting is optional for X509 authentication.

Example: ``PCMK_cr1_file="/etc/pacemaker/crl.pem"``

* - .. _pcmk_key_file:

.. index::
pair: node option; PCMK_key_file

PCMK_key_file
- :ref:`text <text>`
-
- The location of a file containing the private key for the matching
:ref:`PCMK_cert_file <PCMK_cert_file>`, in PEM format. This file must
be readble by Pacemaker daemons (that is, it must allow read permissions
to either the |CRM_DAEMON_USER| user or the |CRM_DAEMON_GROUP| group).
If set, along with :ref:`PCMK_ca_file <PCMK_ca_file>` and
:ref:`PCMK_cert_file <PCMK_cert_file>`, X509 authentication will be
enabled for :ref:`Pacemaker Remote <pacemaker_remote>` and remote CIB
connections.

Example: ``PCMK_key_file="/etc/pacemaker/server.key.pem"``

* - .. _pcmk_authkey_location:

.. index::
pair: node option; PCMK_authkey_location

PCMK_authkey_location
- :ref:`text <text>`
- |PCMK_AUTHKEY_FILE|
- As an alternative to using X509 authentication for :ref:`Pacemaker Remote
<pacemaker_remote>` connections, use the contents of this file as the
authorization key. This file must be readable by Pacemaker daemons (that
is, it must allow read permissions to either the |CRM_DAEMON_USER| user
or the |CRM_DAEMON_GROUP| group), and its contents must be identical on
all nodes.

This is an alternative to using X509 certificates.

* - .. _pcmk_remote_pid1:

.. index::
Expand Down
44 changes: 27 additions & 17 deletions etc/sysconfig/pacemaker.in
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,6 @@

## Pacemaker Remote and remote CIB administration

# PCMK_authkey_location
#
# Use the contents of this file as the authorization key to use with Pacemaker
# Remote connections. This file must be readable by Pacemaker daemons (that is,
# it must allow read permissions to either the @CRM_DAEMON_USER@ user or the
# @CRM_DAEMON_GROUP@ group), and its contents must be identical on all nodes.
#
# Default: PCMK_authkey_location="@PACEMAKER_CONFIG_DIR@/authkey"

# PCMK_remote_address
#
# By default, if the Pacemaker Remote service is run on the local node, it will
Expand All @@ -231,36 +222,55 @@
# 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.
# verify client or server certificates. This file must be in PEM format and
# must be readable by Pacemaker daemons (that is, it must allow read permissions
# to either the @CRM_DAEMON_USER@ user or the @CRM_DAEMON_GROUP@ group).
# If set, along with PCMK_key_file and PCMK_cert_file, X509 authentication
# will be enabled for remote CIB connections.
# will be enabled for Pacemaker Remote and remote CIB connections.
#
# Default: PCMK_ca_file=""

# PCMK_cert_file
#
# The location of a file containing the signed certificate for the server
# (CIB manager) side of the connection, in PEM format. If set, along with
# PCMK_ca_file and PCMK_key_file, X509 authentication will be enabled for
# remote CIB connections.
# side of the connection. This file must be in PEM format and must be
# readable by Pacemaker daemons (that is, it must allow read permissions
# to either the @CRM_DAEMON_USER@ user or the @CRM_DAEMON_GROUP@ group).
# If set, along with PCMK_ca_file and PCMK_key_file, X509 authentication
# will be enabled for Pacemaker Remote and remote CIB connections.
#
# Default: PCMK_cert_file=""

# PCMK_crl_file
#
# The location of a Certificate Revocation List file, in PEM format. This
# 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.
# in PEM format. This file must be readble by Pacemaker daemons (that is, it
# must allow read permissions to either the @CRM_DAEMON_USER@ user or the
# @CRM_DAEMON_GROUP@ group). If set, along with PCMK_ca_file and PCMK_cert_file,
# X509 authentication will be enabled for Pacemaker Remote and remote CIB
# connections.
#
# Default: PCMK_key_file=""

# PCMK_authkey_location
#
# As an alternative to using X509 authentication for Pacemaker Remote
# connections, use the contents of this file as the authorization key. This
# file must be readable by Pacemaker daemons (that is, it must allow read
# permissions to either the @CRM_DAEMON_USER@ user or the @CRM_DAEMON_GROUP@
# group), and its contents must be identical on all nodes.
#
# This is an alternative to using X509 certificates.
#
# Default: PCMK_authkey_location="@PACEMAKER_CONFIG_DIR@/authkey"

# PCMK_remote_pid1 (Advanced Use Only)
#
# When a bundle resource's "run-command" option is left to default, Pacemaker
Expand Down
4 changes: 1 addition & 3 deletions include/crm/common/tls_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,10 @@ 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
}
Expand Down
2 changes: 1 addition & 1 deletion lib/cib/cib_remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ cib_tls_signon(cib_t *cib, pcmk__remote_t *connection, gboolean event_channel)
}

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);
Expand Down
52 changes: 33 additions & 19 deletions lib/common/tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ get_gnutls_priorities(gnutls_credentials_type_t cred_type)
prio_base = PCMK__GNUTLS_PRIORITIES;
}

return crm_strdup_printf("%s:%s", prio_base,
(cred_type == GNUTLS_CRD_ANON)? "+ANON-DH" : "+DHE-PSK:+PSK");
if (cred_type == GNUTLS_CRD_ANON) {
return crm_strdup_printf("%s:+ANON-DH", prio_base);
} else if (cred_type == GNUTLS_CRD_PSK) {
return crm_strdup_printf("%s:+DHE-PSK:+PSK", prio_base);
} else {
return strdup(prio_base);
}
}

static const char *
Expand Down Expand Up @@ -200,6 +205,7 @@ pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_ty
rc = pcmk__init_tls_dh(&(*tls)->dh_params);
if (rc != pcmk_rc_ok) {
pcmk__free_tls(*tls);
*tls = NULL;
return rc;
}
}
Expand All @@ -216,16 +222,26 @@ pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_ty
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");
}

Expand All @@ -240,6 +256,7 @@ pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_ty
rc = tls_load_x509_data(*tls);
if (rc != pcmk_rc_ok) {
pcmk__free_tls(*tls);
*tls = NULL;
return rc;
}
} else if (cred_type == GNUTLS_CRD_PSK) {
Expand Down Expand Up @@ -510,20 +527,17 @@ pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_sec,
}

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")));
}
Loading