blob: 2a1e2338f102d01847566e582e8bb64a40dc93e3 [file] [log] [blame]
/*****************************************************************************\
* tls_s2n.c
*****************************************************************************
* Copyright (C) SchedMD LLC.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include <s2n.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/time.h>
#include "slurm/slurm.h"
#include "slurm/slurm_errno.h"
#include "src/common/slurm_xlator.h"
#include "src/common/fd.h"
#include "src/common/log.h"
#include "src/common/macros.h"
#include "src/common/read_config.h"
#include "src/common/run_in_daemon.h"
#include "src/common/slurm_time.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/slurmd/slurmd/slurmd.h"
#include "src/interfaces/certgen.h"
#include "src/interfaces/certmgr.h"
#include "src/interfaces/conn.h"
/* Set default security policy to FIPS-compliant version */
#define DEFAULT_S2N_SECURITY_POLICY "20230317"
#define CTIME_STR_LEN 72
const char plugin_name[] = "s2n tls plugin";
const char plugin_type[] = "tls/s2n";
const uint32_t plugin_id = TLS_PLUGIN_S2N;
const uint32_t plugin_version = SLURM_VERSION_NUMBER;
/* Default client/server s2n_config objects used for most connections */
static struct s2n_config *client_config = NULL;
static struct s2n_config *server_config = NULL;
/*
* If non-NULL, own_cert_and_key was successfully loaded into
* both server_config/client_config
*/
static struct s2n_cert_chain_and_key *own_cert_and_key = NULL;
static char *own_cert = NULL;
static uint32_t own_cert_len = 0;
static char *own_key = NULL;
static uint32_t own_key_len = 0;
static bool is_own_cert_loaded = false;
static bool is_own_cert_trusted_by_ca = false;
static pthread_rwlock_t s2n_conf_lock = PTHREAD_RWLOCK_INITIALIZER;
static uint32_t s2n_conf_conn_cnt = 0;
static pthread_mutex_t s2n_conf_cnt_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t s2n_conf_cnt_cond = PTHREAD_COND_INITIALIZER;
/*
* These are defined here so when we link with something other than
* the slurmctld we will have these symbols defined. They will get
* overwritten when linking with the slurmctld.
*/
#if defined (__APPLE__)
extern slurmd_conf_t *conf __attribute__((weak_import));
#else
slurmd_conf_t *conf = NULL;
#endif
typedef struct {
int index; /* MUST ALWAYS BE FIRST. DO NOT PACK. */
int input_fd;
int output_fd;
bool maybe;
struct s2n_connection *s2n_conn;
/* absolute time shutdown() delayed until (instead of sleep()ing) */
timespec_t delay;
struct s2n_config *s2n_config;
struct s2n_cert_chain_and_key *cert_and_key;
bool do_graceful_shutdown;
bool using_global_s2n_conf;
bool is_client_authenticated;
} tls_conn_t;
/*
* Handle and log a libs2n function failing
* IN conn - ptr to connection or NULL
* IN func - function that failed
*/
#define on_s2n_error(conn, func) \
_on_s2n_error(conn, (void *(*)(void)) func, XSTRINGIFY(func), __func__)
static void _on_s2n_error(tls_conn_t *conn, void *(*func_ptr)(void),
const char *funcname, const char *caller)
{
/* Save errno now in case error() clobbers it */
const int orig_errno = errno;
const int error_type = s2n_error_get_type(s2n_errno);
/*
* Per libs2n docs:
* After s2n_recv() or s2n_negotiate() return an error, the
* application must call s2n_connection_get_delay() and pause
* activity on the connection for the specified number of
* nanoseconds before calling s2n_shutdown(), close(), or
* shutdown()
*/
if ((func_ptr == (void *) s2n_negotiate) ||
(func_ptr == (void *) s2n_recv)) {
timespec_t delay = { 0 };
if ((delay.tv_nsec =
s2n_connection_get_delay(conn->s2n_conn))) {
const timespec_t now = timespec_now();
delay = timespec_normalize(delay);
if (timespec_is_after(conn->delay, now))
conn->delay = timespec_add(conn->delay, delay);
else
conn->delay = timespec_add(now, delay);
if (slurm_conf.debug_flags & DEBUG_FLAG_TLS) {
char str[CTIME_STR_LEN];
timespec_ctime(conn->delay, true, str,
sizeof(str));
log_flag(TLS, "%s: %s() failed %s[%d] requiring shutdown() be delayed until %s",
caller, funcname,
s2n_strerror_name(s2n_errno),
s2n_errno, str);
}
}
}
if (error_type == S2N_ERR_T_ALERT) {
int alert = S2N_ERR_T_OK;
if (conn)
alert = s2n_connection_get_alert(conn->s2n_conn);
else
fatal_abort("%s: s2n alert without connection",
__func__);
xassert(alert != S2N_ERR_T_OK);
error("%s: %s() alerted %s[%d]: %s -> %s",
caller, funcname, s2n_strerror_name(alert), alert,
s2n_strerror(alert, NULL),
s2n_strerror_debug(alert, NULL));
} else if (!conn->maybe) {
xassert(s2n_errno != S2N_ERR_T_OK);
error("%s: %s() failed %s[%d]: %s -> %s",
caller, funcname, s2n_strerror_name(s2n_errno), s2n_errno,
s2n_strerror(s2n_errno, NULL),
s2n_strerror_debug(s2n_errno, NULL));
}
if (!conn->maybe && (slurm_conf.debug_flags & DEBUG_FLAG_TLS) &&
s2n_stack_traces_enabled())
s2n_print_stacktrace(log_fp());
/* Map the s2n error to a Slurm error (as closely as possible) */
switch (s2n_error_get_type(s2n_errno)) {
case S2N_ERR_T_BLOCKED:
errno = EWOULDBLOCK;
break;
case S2N_ERR_T_CLOSED:
errno = SLURM_COMMUNICATIONS_SHUTDOWN_ERROR;
break;
case S2N_ERR_T_IO:
/* I/O errors should set errno */
if (orig_errno)
errno = orig_errno;
else
errno = EIO;
break;
case S2N_ERR_T_PROTO:
errno = SLURM_PROTOCOL_AUTHENTICATION_ERROR;
break;
case S2N_ERR_T_ALERT:
errno = SLURM_PROTOCOL_AUTHENTICATION_ERROR;
break;
default:
errno = SLURM_ERROR;
break;
}
/* Per library docs:
* NOTE: To avoid possible confusion, s2n_errno should be cleared
* after processing an error
*/
s2n_errno = S2N_ERR_T_OK;
}
static int _check_file_permissions(const char *path, int bad_perms,
bool check_owner)
{
struct stat statbuf;
xassert(path);
if (stat(path, &statbuf)) {
debug("%s: cannot stat '%s': %m", plugin_type, path);
return ENOENT;
}
/*
* Configless operation means slurm_user_id is 0.
* Avoid an incorrect warning if the key is actually owned by the
* (currently unknown) SlurmUser. (Although if you're running with
* SlurmUser=root, this warning will be skipped inadvertently.)
*/
if (check_owner && (statbuf.st_uid != 0) && slurm_conf.slurm_user_id &&
(statbuf.st_uid != slurm_conf.slurm_user_id)) {
debug("%s: '%s' owned by uid=%u, instead of SlurmUser(%u) or root",
plugin_type, path, statbuf.st_uid,
slurm_conf.slurm_user_id);
return SLURM_ERROR;
}
if (statbuf.st_mode & bad_perms) {
debug("%s: file is insecure: '%s' mode=0%o",
plugin_type, path, statbuf.st_mode & 0777);
return SLURM_ERROR;
}
return SLURM_SUCCESS;
}
/*
* Note: function signature and returns are dictated by s2n library.
* Return 1 to trust that hostname or 0 to not trust the hostname.
*/
static uint8_t _verify_hostname(const char *host_name, size_t host_name_len,
void *data)
{
return 1;
}
static struct s2n_config *_create_config(void)
{
struct s2n_config *new_conf = NULL;
char *security_policy = NULL;
if (xstrstr(slurm_conf.tls_params, "load_system_certificates")) {
if (!(new_conf = s2n_config_new())) {
on_s2n_error(NULL, s2n_config_new);
return NULL;
}
} else {
if (!(new_conf = s2n_config_new_minimal())) {
on_s2n_error(NULL, s2n_config_new_minimal);
return NULL;
}
}
/*
* Get security policy version.
*
* https://aws.github.io/s2n-tls/usage-guide/ch06-security-policies.html
*/
security_policy = conf_get_opt_str(slurm_conf.tls_params,
"security_policy_version=");
if (!security_policy)
security_policy = xstrdup(DEFAULT_S2N_SECURITY_POLICY);
if (s2n_config_set_cipher_preferences(new_conf, security_policy) < 0) {
on_s2n_error(NULL, s2n_config_set_cipher_preferences);
xfree(security_policy);
(void) s2n_config_free(new_conf);
return NULL;
}
xfree(security_policy);
if (s2n_config_set_verify_host_callback(new_conf, _verify_hostname,
NULL) < 0) {
on_s2n_error(NULL, s2n_config_set_verify_host_callback);
(void) s2n_config_free(new_conf);
return NULL;
}
/*
* Per libs2n docs:
* Server behavior:
* - Optional: Request the client's certificate and validate it. If no
* certificate is received then no validation is performed.
* Client behavior:
* - Optional (default): Sends the client certificate if the server
* requests client authentication. No certificate is sent if the
* application hasn't provided a certificate.
*/
if (s2n_config_set_client_auth_type(new_conf, S2N_CERT_AUTH_OPTIONAL)) {
on_s2n_error(NULL, s2n_config_set_client_auth_type);
return NULL;
}
return new_conf;
}
static char *_get_ca_cert_file_from_conf(void)
{
char *cert;
/* Get explicit path configuration */
if ((cert = conf_get_opt_str(slurm_conf.tls_params, "ca_cert_file=")))
return cert;
/* Try to find default path */
if ((cert = get_extra_conf_path("ca_cert.pem")))
return cert;
return NULL;
}
static int _add_ca_cert_to_config(struct s2n_config *config, char *cert_file)
{
buf_t *cert_buf;
/*
* Check if CA cert is owned by SlurmUser/root and that it's not
* modifiable/executable by everyone.
*/
if (_check_file_permissions(cert_file, (S_IWOTH | S_IXOTH), true)) {
return SLURM_ERROR;
}
if (!(cert_buf = create_mmap_buf(cert_file))) {
error("%s: Could not load CA cert file (%s)",
plugin_type, cert_file);
return SLURM_ERROR;
}
if (s2n_config_add_pem_to_trust_store(config, cert_buf->head)) {
on_s2n_error(NULL, s2n_config_add_pem_to_trust_store);
FREE_NULL_BUFFER(cert_buf);
return SLURM_ERROR;
}
FREE_NULL_BUFFER(cert_buf);
return SLURM_SUCCESS;
}
static int _create_cert_and_key(struct s2n_cert_chain_and_key **cert_and_key,
char *cert_pem, uint32_t cert_pem_len,
char *key_pem, uint32_t key_pem_len)
{
xassert(cert_and_key);
if (*cert_and_key &&
(s2n_cert_chain_and_key_free(*cert_and_key) != S2N_SUCCESS)) {
on_s2n_error(NULL, s2n_cert_chain_and_key_free);
}
*cert_and_key = NULL;
if (!(*cert_and_key = s2n_cert_chain_and_key_new())) {
on_s2n_error(NULL, s2n_cert_chain_and_key_new);
return SLURM_ERROR;
}
if (s2n_cert_chain_and_key_load_pem_bytes(*cert_and_key,
(uint8_t *) cert_pem,
cert_pem_len,
(uint8_t *) key_pem,
key_pem_len) < 0) {
on_s2n_error(NULL, s2n_cert_chain_and_key_load_pem_bytes);
return SLURM_ERROR;
}
return SLURM_SUCCESS;
}
static int _add_own_cert_to_config(struct s2n_config *s2n_config,
struct s2n_cert_chain_and_key **cert_and_key)
{
/*
* Per libs2n docs:
* It is not recommended to free or modify the `cert_key_pair` as
* any subsequent changes will be reflected in the config.
*/
if (s2n_config_add_cert_chain_and_key_to_store(s2n_config,
*cert_and_key)) {
on_s2n_error(NULL, s2n_config_add_cert_chain_and_key_to_store);
goto fail;
}
return SLURM_SUCCESS;
fail:
if (*cert_and_key &&
(s2n_cert_chain_and_key_free(*cert_and_key) != S2N_SUCCESS)) {
on_s2n_error(NULL, s2n_cert_chain_and_key_free);
}
*cert_and_key = NULL;
return SLURM_ERROR;
}
static int _reset_global_conf(struct s2n_config **config)
{
int rc = SLURM_ERROR;
char *cert_file = NULL;
if (*config && s2n_config_free(*config))
on_s2n_error(NULL, s2n_config_free);
if (!(*config = _create_config()))
goto end;
if (!(cert_file = _get_ca_cert_file_from_conf()))
goto end;
if (_add_ca_cert_to_config(*config, cert_file))
goto end;
if (_add_own_cert_to_config(*config, &own_cert_and_key))
goto end;
rc = SLURM_SUCCESS;
end:
xfree(cert_file);
return rc;
}
static int _add_cert_to_global_config(char *cert_pem, uint32_t cert_pem_len,
char *key_pem, uint32_t key_pem_len,
bool server_only)
{
if (!own_cert_and_key) {
is_own_cert_loaded = true;
if (_create_cert_and_key(&own_cert_and_key, cert_pem,
cert_pem_len, key_pem, key_pem_len))
return SLURM_ERROR;
if (_add_own_cert_to_config(server_config, &own_cert_and_key))
return SLURM_ERROR;
if (!server_only &&
_add_own_cert_to_config(client_config, &own_cert_and_key))
return SLURM_ERROR;
return SLURM_SUCCESS;
}
/* Stop new connections from using current server_config */
slurm_rwlock_wrlock(&s2n_conf_lock);
log_flag(TLS, "%s: Updating global server_conf with new certificate and key now...", plugin_type);
/* Wait until connections using server_config are finished */
slurm_mutex_lock(&s2n_conf_cnt_lock);
if (s2n_conf_conn_cnt)
slurm_cond_wait(&s2n_conf_cnt_cond, &s2n_conf_cnt_lock);
if (_create_cert_and_key(&own_cert_and_key, cert_pem, cert_pem_len,
key_pem, key_pem_len))
goto fail;
/*
* s2n_config_free_cert_chain_and_key not enough to reset the
* certificate associated with server_config. server_config must be
* recreated.
*/
if (_reset_global_conf(&server_config))
goto fail;
if (!server_only && _reset_global_conf(&client_config))
goto fail;
slurm_mutex_unlock(&s2n_conf_cnt_lock);
log_flag(TLS, "%s: Successfully updated global server_conf with new certificate and key", plugin_type);
slurm_rwlock_unlock(&s2n_conf_lock);
return SLURM_SUCCESS;
fail:
slurm_mutex_unlock(&s2n_conf_cnt_lock);
log_flag(TLS, "%s: Failed to update global server_conf with new certificate and key", plugin_type);
slurm_rwlock_unlock(&s2n_conf_lock);
return SLURM_ERROR;
}
static char *_get_cert_or_key_path(char *conf_opt, char *default_path)
{
char *file_path = NULL;
file_path = conf_get_opt_str(slurm_conf.tls_params, conf_opt);
if (!file_path)
file_path = get_extra_conf_path(default_path);
/* Expand %h and %n in path for slurmd */
if (running_in_slurmd() && conf) {
char *tmp;
slurm_conf_lock();
tmp = slurm_conf_expand_slurmd_path(file_path, conf->node_name,
NULL);
slurm_conf_unlock();
xfree(file_path);
file_path = tmp;
}
return file_path;
}
static int _add_cert_from_file_to_server(void)
{
int rc = SLURM_SUCCESS;
char *cert_file = NULL, *key_file = NULL;
char *cert_conf = NULL, *key_conf = NULL;
char *default_cert_path = NULL, *default_key_path = NULL;
buf_t *cert_buf = NULL, *key_buf = NULL;
bool check_owner = true;
if (running_in_slurmdbd()) {
cert_conf = "dbd_cert_file=";
key_conf = "dbd_cert_key_file=";
default_cert_path = "dbd_cert.pem";
default_key_path = "dbd_cert_key.pem";
} else if (running_in_slurmrestd()) {
cert_conf = "restd_cert_file=";
key_conf = "restd_cert_key_file=";
default_cert_path = "restd_cert.pem";
default_key_path = "restd_cert_key.pem";
check_owner = false;
} else if (running_in_slurmctld()) {
cert_conf = "ctld_cert_file=";
key_conf = "ctld_cert_key_file=";
default_cert_path = "ctld_cert.pem";
default_key_path = "ctld_cert_key.pem";
} else if (running_in_slurmd()) {
cert_conf = "slurmd_cert_file=";
key_conf = "slurmd_cert_key_file=";
default_cert_path = "slurmd_cert.pem";
default_key_path = "slurmd_cert_key.pem";
} else if (running_in_sackd()) {
cert_conf = "sackd_cert_file=";
key_conf = "sackd_cert_key_file=";
default_cert_path = "sackd_cert.pem";
default_key_path = "sackd_cert_key.pem";
} else {
return SLURM_ERROR;
}
if (!(cert_file = _get_cert_or_key_path(cert_conf, default_cert_path))) {
error("Failed to get %s path", default_cert_path);
rc = SLURM_ERROR;
goto cleanup;
}
/*
* Check if our public certificate is owned by SlurmUser/root (unless
* running in slurmrestd) and that it's not modifiable/executable by
* everyone.
*/
if ((rc = _check_file_permissions(cert_file, (S_IWOTH | S_IXOTH),
check_owner))) {
/*
* If no static certificate was found, get a signed one from
* slurmctld later.
*/
if ((rc == ENOENT) &&
(running_in_slurmd() || running_in_sackd()))
rc = SLURM_SUCCESS;
goto cleanup;
}
if (!(cert_buf = create_mmap_buf(cert_file))) {
error("%s: Could not load cert file (%s): %m",
plugin_type, cert_file);
rc = SLURM_ERROR;
goto cleanup;
}
xfree(cert_file);
if (!(key_file = _get_cert_or_key_path(key_conf, default_key_path))) {
error("Failed to get %s path", default_key_path);
rc = SLURM_ERROR;
goto cleanup;
}
/*
* Check if our private key is owned by SlurmUser/root (unless running
* in slurmrestd) and that it's not readable/writable/executable by
* everyone.
*/
if (_check_file_permissions(key_file, S_IRWXO, check_owner)) {
rc = SLURM_ERROR;
goto cleanup;
}
if (!(key_buf = create_mmap_buf(key_file))) {
error("%s: Could not load private key file (%s): %m",
plugin_type, key_file);
rc = SLURM_ERROR;
goto cleanup;
}
rc = _add_cert_to_global_config(cert_buf->head, cert_buf->size,
key_buf->head, key_buf->size, false);
cleanup:
xfree(key_file);
xfree(cert_file);
FREE_NULL_BUFFER(cert_buf);
FREE_NULL_BUFFER(key_buf);
return rc;
}
extern int tls_p_load_self_signed_cert(void)
{
if (certgen_g_self_signed(&own_cert, &own_key) || !own_cert ||
!own_key) {
error("Failed to generate self signed certificate and private key");
return SLURM_ERROR;
}
own_cert_len = strlen(own_cert);
own_key_len = strlen(own_key);
/* Don't add self-signed cert to client_config */
return _add_cert_to_global_config(own_cert, own_cert_len, own_key,
own_key_len, true);
}
extern int init(void)
{
static bool init_run = false;
if (init_run)
return SLURM_SUCCESS;
init_run = true;
if (s2n_init() != S2N_SUCCESS) {
on_s2n_error(NULL, s2n_init);
return errno;
}
#ifdef NDEBUG
/* Always disable backtraces unless compiled in developer mode */
s2n_stack_traces_enabled_set(false);
#else
/* Only enable backtraces with TLS debugflag active */
s2n_stack_traces_enabled_set(slurm_conf.debug_flags & DEBUG_FLAG_TLS);
#endif
/* Create client s2n_config */
if (!(client_config = _create_config())) {
error("Could not create client configuration for s2n");
return errno;
}
/* Create server s2n_config */
if (!(server_config = _create_config())) {
error("Could not create server configuration for s2n");
return errno;
}
/*
* Description of OpenSSL versions:
* https://docs.openssl.org/1.1.1/man3/OPENSSL_VERSION_NUMBER/#description
*/
debug("Initialized %s. DEBUG_FLAG_TLS:%s s2n_stack_traces_enabled:%s s2n_get_openssl_version:0x%09zx",
plugin_type,
BOOL_STRINGIFY(slurm_conf.debug_flags & DEBUG_FLAG_TLS),
BOOL_STRINGIFY(s2n_stack_traces_enabled()),
s2n_get_openssl_version());
return SLURM_SUCCESS;
}
extern void fini(void)
{
static bool fini_run = false;
if (fini_run)
return;
fini_run = true;
if (s2n_config_free(client_config))
on_s2n_error(NULL, s2n_config_free);
if (server_config &&
s2n_config_free_cert_chain_and_key(server_config)) {
on_s2n_error(NULL, s2n_config_free_cert_chain_and_key);
}
if (own_cert_and_key &&
(s2n_cert_chain_and_key_free(own_cert_and_key) != S2N_SUCCESS))
on_s2n_error(NULL, s2n_cert_chain_and_key_free);
if (s2n_config_free(server_config))
on_s2n_error(NULL, s2n_config_free);
if (s2n_cleanup_final())
on_s2n_error(NULL, s2n_cleanup_final);
xfree(own_cert);
xfree(own_key);
}
static int _negotiate(tls_conn_t *conn)
{
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
if (s2n_negotiate(conn->s2n_conn, &blocked) != S2N_SUCCESS) {
if (s2n_error_get_type(s2n_errno) == S2N_ERR_T_BLOCKED) {
/* Avoid calling on_s2n_error for blocking */
return EWOULDBLOCK;
} else {
on_s2n_error(conn, s2n_negotiate);
return errno;
}
}
if (s2n_connection_client_cert_used(conn->s2n_conn) == 1)
conn->is_client_authenticated = true;
if (slurm_conf.debug_flags & DEBUG_FLAG_TLS) {
const char *cipher = NULL;
uint8_t first = 0, second = 0;
if (!(cipher = s2n_connection_get_cipher(conn->s2n_conn))) {
on_s2n_error(conn, s2n_connection_get_cipher);
}
if (s2n_connection_get_cipher_iana_value(conn->s2n_conn, &first,
&second) < 0) {
on_s2n_error(conn,
s2n_connection_get_cipher_iana_value);
}
log_flag(TLS, "%s: cipher suite:%s, {0x%02X,0x%02X}%s. fd:%d->%d.",
plugin_type, cipher, first, second,
conn->is_client_authenticated ?
" Client is authenticated (mTLS)" :
"",
conn->input_fd, conn->output_fd);
}
return SLURM_SUCCESS;
}
extern int tls_p_load_ca_cert(char *cert_file)
{
int rc;
bool free_cert = false;
if (!cert_file) {
if (!(cert_file = _get_ca_cert_file_from_conf()))
return SLURM_ERROR;
free_cert = true;
}
rc = (_add_ca_cert_to_config(client_config, cert_file) ||
_add_ca_cert_to_config(server_config, cert_file));
if (free_cert)
xfree(cert_file);
return rc;
}
extern char *tls_p_get_own_public_cert(void)
{
log_flag(AUDIT_TLS, "Returning own public cert: \n%s", own_cert);
return xstrdup(own_cert);
}
extern int tls_p_load_own_cert(char *cert, uint32_t cert_len, char *key,
uint32_t key_len)
{
if (!cert) {
if (_add_cert_from_file_to_server())
return SLURM_ERROR;
goto cert_loaded;
}
xfree(own_cert);
xfree(own_key);
/* Save certificate details for later */
own_cert = xstrdup(cert);
own_cert_len = cert_len;
own_key = xstrdup(key);
own_key_len = key_len;
if (_add_cert_to_global_config(cert, cert_len, key, key_len, false)) {
error("%s: Could not add certificate and private key to s2n_config.",
plugin_type);
return SLURM_ERROR;
}
cert_loaded:
log_flag(TLS, "Successfully loaded signed certificate into TLS store.");
is_own_cert_trusted_by_ca = true;
return SLURM_SUCCESS;
}
extern bool tls_p_own_cert_loaded(void)
{
return is_own_cert_loaded;
}
static void _s2n_config_inc(tls_conn_t *conn)
{
slurm_mutex_lock(&s2n_conf_cnt_lock);
s2n_conf_conn_cnt++;
slurm_mutex_unlock(&s2n_conf_cnt_lock);
}
static void _s2n_config_dec(tls_conn_t *conn)
{
slurm_mutex_lock(&s2n_conf_cnt_lock);
if (s2n_conf_conn_cnt > 0) {
s2n_conf_conn_cnt--;
} else {
error("%s: unexpected underflow", __func__);
}
if (s2n_conf_conn_cnt == 0) {
slurm_cond_signal(&s2n_conf_cnt_cond);
}
slurm_mutex_unlock(&s2n_conf_cnt_lock);
}
static void _cleanup_tls_conn(tls_conn_t *conn)
{
if (conn->s2n_config && (conn->s2n_config != server_config) &&
(conn->s2n_config != client_config))
if (s2n_config_free(conn->s2n_config))
on_s2n_error(conn, s2n_config_free);
if (conn->cert_and_key &&
(s2n_cert_chain_and_key_free(conn->cert_and_key) != S2N_SUCCESS))
on_s2n_error(conn, s2n_cert_chain_and_key_free);
if (conn->s2n_conn && s2n_connection_free(conn->s2n_conn) < 0)
on_s2n_error(conn, s2n_connection_free);
if (conn->using_global_s2n_conf)
_s2n_config_dec(conn);
if (s2n_stack_traces_enabled())
s2n_free_stacktrace();
}
static int _set_conn_s2n_conf(tls_conn_t *conn,
const conn_args_t *tls_conn_args)
{
char *cert_file = NULL;
bool is_server = (tls_conn_args->mode == TLS_CONN_SERVER);
if (!slurm_rwlock_tryrdlock(&s2n_conf_lock)) {
if (is_server && !own_cert_and_key) {
error("%s: No own certificate has been loaded yet, cannot create connection for fd:%d->%d",
__func__, tls_conn_args->input_fd,
tls_conn_args->output_fd);
slurm_rwlock_unlock(&s2n_conf_lock);
return SLURM_ERROR;
}
_s2n_config_inc(conn);
if (is_server)
conn->s2n_config = server_config;
else
conn->s2n_config = client_config;
conn->using_global_s2n_conf = true;
slurm_rwlock_unlock(&s2n_conf_lock);
return SLURM_SUCCESS;
}
/*
* Create a new s2n_config with the same certificate while the
* old one is getting updated.
*/
log_flag(TLS, "%s: global s2n_config is being updated, creating new s2n_config for conn to fd:%d->%d",
plugin_type, tls_conn_args->input_fd, tls_conn_args->output_fd);
if (!own_cert || !own_cert_len || !own_key || !own_key_len) {
error("%s: Global s2n_config is busy being updated and there's no saved certificate/key to create a temporary s2n_config for conn to fd:%d->%d",
__func__, tls_conn_args->input_fd,
tls_conn_args->output_fd);
return SLURM_ERROR;
}
if (!(conn->s2n_config = _create_config())) {
error("Could not create s2n_config for fd:%d->%d",
tls_conn_args->input_fd, tls_conn_args->output_fd);
return SLURM_ERROR;
}
if (_create_cert_and_key(&conn->cert_and_key, own_cert, own_cert_len,
own_key, own_key_len)) {
error("Could not cert and key pair for fd:%d->%d",
tls_conn_args->input_fd, tls_conn_args->output_fd);
return SLURM_ERROR;
}
if (_add_own_cert_to_config(conn->s2n_config, &conn->cert_and_key)) {
error("Could not add certificate to s2n_config for fd:%d->%d",
tls_conn_args->input_fd, tls_conn_args->output_fd);
return SLURM_ERROR;
}
if (!(cert_file = _get_ca_cert_file_from_conf())) {
error("Could not get CA certificate file for s2n_config for fd:%d->%d",
tls_conn_args->input_fd, tls_conn_args->output_fd);
return SLURM_ERROR;
}
if (_add_ca_cert_to_config(conn->s2n_config, cert_file)) {
error("Could not add certificate to s2n_config for fd:%d->%d",
tls_conn_args->input_fd, tls_conn_args->output_fd);
xfree(cert_file);
return SLURM_ERROR;
}
xfree(cert_file);
return SLURM_SUCCESS;
}
extern void *tls_p_create_conn(const conn_args_t *tls_conn_args)
{
tls_conn_t *conn;
s2n_mode s2n_conn_mode;
log_flag(TLS, "%s: create connection. fd:%d->%d. tls mode:%s",
plugin_type, tls_conn_args->input_fd, tls_conn_args->output_fd,
conn_mode_to_str(tls_conn_args->mode));
conn = xmalloc(sizeof(*conn));
conn->input_fd = tls_conn_args->input_fd;
conn->output_fd = tls_conn_args->output_fd;
conn->maybe = tls_conn_args->maybe;
switch (tls_conn_args->mode) {
case TLS_CONN_SERVER:
{
s2n_conn_mode = S2N_SERVER;
break;
}
case TLS_CONN_CLIENT:
{
s2n_conn_mode = S2N_CLIENT;
if (!tls_conn_args->cert) {
log_flag(TLS, "%s: no certificate provided for new connection. Using default trust store.",
plugin_type);
break;
}
log_flag(TLS, "%s: using cert to create connection:\n%s",
plugin_type, tls_conn_args->cert);
/* Use new config with only "cert" loaded in trust store */
if (!(conn->s2n_config = _create_config())) {
error("Failed to create new config for connection");
goto fail;
}
if (s2n_config_add_pem_to_trust_store(conn->s2n_config,
tls_conn_args->cert)) {
on_s2n_error(conn, s2n_config_add_pem_to_trust_store);
goto fail;
}
break;
}
default:
error("Invalid tls connection mode");
goto fail;
}
if (!conn->s2n_config && _set_conn_s2n_conf(conn, tls_conn_args))
goto fail;
if (!(conn->s2n_conn = s2n_connection_new(s2n_conn_mode))) {
on_s2n_error(conn, s2n_connection_new);
goto fail;
}
if (s2n_connection_set_config(conn->s2n_conn, conn->s2n_config) < 0) {
on_s2n_error(conn, s2n_connection_set_config);
goto fail;
}
if (tls_conn_args->defer_blinding &&
s2n_connection_set_blinding(conn->s2n_conn,
S2N_SELF_SERVICE_BLINDING)) {
on_s2n_error(conn, s2n_connection_set_blinding);
goto fail;
}
if (tls_conn_args->callbacks.recv) {
void *io_context = tls_conn_args->callbacks.io_context;
if (s2n_connection_set_recv_cb(conn->s2n_conn,
tls_conn_args->callbacks.recv)) {
on_s2n_error(conn, s2n_connection_set_recv_cb);
goto fail;
}
if (s2n_connection_set_recv_ctx(conn->s2n_conn, io_context)) {
on_s2n_error(conn, s2n_connection_set_recv_ctx);
goto fail;
}
xassert(tls_conn_args->input_fd < 0);
xassert(io_context);
} else if (s2n_connection_set_read_fd(conn->s2n_conn,
tls_conn_args->input_fd) < 0) {
/* Associate a connection with an incoming descriptor */
on_s2n_error(conn, s2n_connection_set_read_fd);
goto fail;
}
if (tls_conn_args->callbacks.send) {
void *io_context = tls_conn_args->callbacks.io_context;
if (s2n_connection_set_send_cb(conn->s2n_conn,
tls_conn_args->callbacks.send)) {
on_s2n_error(conn, s2n_connection_set_send_cb);
goto fail;
}
if (s2n_connection_set_send_ctx(conn->s2n_conn, io_context)) {
on_s2n_error(conn, s2n_connection_set_send_ctx);
goto fail;
}
xassert(tls_conn_args->output_fd < 0);
xassert(io_context);
} else if (s2n_connection_set_write_fd(conn->s2n_conn,
tls_conn_args->output_fd) < 0) {
/* Associate a connection with an outgoing descriptor */
on_s2n_error(conn, s2n_connection_set_write_fd);
goto fail;
}
if (!tls_conn_args->defer_negotiation) {
int rc;
/* Negotiate the TLS handshake */
while ((rc = _negotiate(conn))) {
if (rc == EWOULDBLOCK) {
if (wait_fd_readable(conn->input_fd,
slurm_conf.msg_timeout))
error("%s: [fd:%d->fd:%d] Problem reading socket during s2n negotiation",
__func__, tls_conn_args->input_fd,
tls_conn_args->output_fd);
else
continue;
}
if (tls_conn_args->defer_blinding)
return conn;
goto fail;
}
}
log_flag(TLS, "%s: connection successfully created. fd:%d->%d. tls mode:%s",
plugin_type, conn->input_fd, conn->output_fd,
conn_mode_to_str(tls_conn_args->mode));
return conn;
fail:
_cleanup_tls_conn(conn);
xfree(conn);
return NULL;
}
extern void tls_p_destroy_conn(tls_conn_t *conn, bool close_fds)
{
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
xassert(conn);
log_flag(TLS, "%s: destroying connection. fd:%d->%d",
plugin_type, conn->input_fd, conn->output_fd);
if (!conn->s2n_conn) {
_cleanup_tls_conn(conn);
xfree(conn);
return;
}
if (conn->do_graceful_shutdown) {
log_flag(TLS, "%s: Attempting s2n_shutdown for fd:%d->%d",
plugin_type, conn->input_fd, conn->output_fd);
} else {
log_flag(TLS, "%s: Skipping s2n_shutdown for fd:%d->%d",
plugin_type, conn->input_fd, conn->output_fd);
}
/* Attempt graceful shutdown at TLS layer */
while (conn->do_graceful_shutdown &&
s2n_shutdown(conn->s2n_conn, &blocked)) {
if (s2n_error_get_type(s2n_errno) != S2N_ERR_T_BLOCKED) {
on_s2n_error(conn, s2n_shutdown);
break;
}
if (wait_fd_readable(conn->input_fd, slurm_conf.msg_timeout) ==
-1) {
error("Problem reading socket, couldn't do graceful s2n shutdown");
break;
}
}
if (close_fds) {
if (conn->input_fd >= 0)
close(conn->input_fd);
if ((conn->output_fd >= 0) &&
(conn->output_fd != conn->input_fd))
close(conn->output_fd);
}
_cleanup_tls_conn(conn);
xfree(conn);
}
extern ssize_t tls_p_send(tls_conn_t *conn, const void *buf, size_t n)
{
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
ssize_t bytes_written = 0;
xassert(conn);
while ((bytes_written < n) && (blocked == S2N_NOT_BLOCKED)) {
ssize_t w = s2n_send(conn->s2n_conn, (buf + bytes_written),
(n - bytes_written), &blocked);
if (w < 0) {
/* blocked is expected */
if (s2n_error_get_type(s2n_errno) != S2N_ERR_T_BLOCKED)
on_s2n_error(conn, s2n_send);
bytes_written = SLURM_ERROR;
break;
}
bytes_written += w;
}
log_flag(TLS, "%s: send %zd. fd:%d->%d",
plugin_type, bytes_written, conn->input_fd, conn->output_fd);
if ((blocked != S2N_NOT_BLOCKED) && !errno)
errno = EWOULDBLOCK;
return bytes_written;
}
extern ssize_t tls_p_sendv(tls_conn_t *conn, const struct iovec *bufs,
int count)
{
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
ssize_t w = 0;
xassert(conn);
if (slurm_conf.debug_flags & DEBUG_FLAG_TLS) {
log_flag(TLS, "%s: s2n_sendv %d bufs. fd:%d->%d:",
plugin_type, count, conn->input_fd, conn->output_fd);
for (int i = 0; i < count; i++) {
log_flag(TLS, "%s: bufs[%d] %zd. fd:%d->%d",
plugin_type, i, bufs[i].iov_len, conn->input_fd,
conn->output_fd);
}
}
if ((w = s2n_sendv(conn->s2n_conn, bufs, count, &blocked)) < 0) {
on_s2n_error(conn, s2n_sendv);
return SLURM_ERROR;
}
return w;
}
extern uint32_t tls_p_peek(tls_conn_t *conn)
{
int readable = 0;
if (!conn)
return 0;
if ((readable = s2n_peek(conn->s2n_conn)))
return readable;
if (fd_get_readable_bytes(conn->input_fd, &readable, NULL) || !readable)
return 0;
return readable;
}
extern ssize_t tls_p_recv(tls_conn_t *conn, void *buf, size_t n)
{
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
ssize_t bytes_read = 0;
xassert(conn);
if (!n) {
ssize_t r = -1;
if (s2n_peek(conn->s2n_conn))
return 0;
if ((r = s2n_recv(conn->s2n_conn, NULL, 0, &blocked)) < 0)
on_s2n_error(conn, s2n_recv);
return r;
}
while (bytes_read < n) {
ssize_t r = s2n_recv(conn->s2n_conn, (buf + bytes_read),
(n - bytes_read), &blocked);
if (r > 0) {
bytes_read += r;
} else if (!r) {
/* connection closed */
break;
} else if (s2n_error_get_type(s2n_errno) == S2N_ERR_T_BLOCKED) {
if (!bytes_read) {
log_flag(TLS, "%s: recv would block. fd:%d->%d",
plugin_type, conn->input_fd,
conn->output_fd);
errno = EWOULDBLOCK;
return -1;
}
/*
* recv() would block so consider the recv() complete
* for now
*/
break;
} else {
on_s2n_error(conn, s2n_recv);
return SLURM_ERROR;
}
}
log_flag(TLS, "%s: recv %zd. fd:%d->%d",
plugin_type, bytes_read, conn->input_fd, conn->output_fd);
return bytes_read;
}
extern timespec_t tls_p_get_delay(tls_conn_t *conn)
{
xassert(conn);
return conn->delay;
}
extern int tls_p_negotiate_conn(tls_conn_t *conn)
{
xassert(conn);
return _negotiate(conn);
}
extern bool tls_p_is_client_authenticated(tls_conn_t *conn)
{
xassert(conn);
return conn->is_client_authenticated;
}
extern int tls_p_get_conn_fd(tls_conn_t *conn)
{
if (!conn)
return -1;
if (conn->input_fd != conn->output_fd)
debug("%s: asymmetric connection %d->%d",
__func__, conn->input_fd, conn->output_fd);
return conn->input_fd;
}
extern int tls_p_set_conn_fds(tls_conn_t *conn, int input_fd, int output_fd)
{
xassert(conn);
xassert(conn->s2n_conn);
xassert(input_fd >= 0);
xassert(output_fd >= 0);
/* Reset read/write callbacks/contexts */
if (s2n_connection_set_recv_cb(conn->s2n_conn, NULL)) {
on_s2n_error(conn, s2n_connection_set_recv_cb);
return SLURM_ERROR;
}
if (s2n_connection_set_recv_ctx(conn->s2n_conn, NULL)) {
on_s2n_error(conn, s2n_connection_set_recv_ctx);
return SLURM_ERROR;
}
if (s2n_connection_set_send_cb(conn->s2n_conn, NULL)) {
on_s2n_error(conn, s2n_connection_set_send_cb);
return SLURM_ERROR;
}
if (s2n_connection_set_send_ctx(conn->s2n_conn, NULL)) {
on_s2n_error(conn, s2n_connection_set_send_ctx);
return SLURM_ERROR;
}
/* Set new read/write fd's */
if (s2n_connection_set_read_fd(conn->s2n_conn, input_fd)) {
on_s2n_error(conn, s2n_connection_set_read_fd);
return SLURM_ERROR;
}
if (s2n_connection_set_write_fd(conn->s2n_conn, output_fd)) {
on_s2n_error(conn, s2n_connection_set_write_fd);
return SLURM_ERROR;
}
conn->input_fd = input_fd;
conn->output_fd = output_fd;
log_flag(TLS, "Successfully set input_fd:%d output_fd:%d on s2n conn %p",
input_fd, output_fd, conn->s2n_conn);
return SLURM_SUCCESS;
}
extern int tls_p_set_conn_callbacks(tls_conn_t *conn,
conn_callbacks_t *callbacks)
{
xassert(conn);
xassert(conn->s2n_conn);
xassert(callbacks);
xassert(callbacks->recv);
xassert(callbacks->send);
/* Set new read/write callbacks/contexts */
if (s2n_connection_set_recv_cb(conn->s2n_conn, callbacks->recv)) {
on_s2n_error(conn, s2n_connection_set_recv_cb);
return SLURM_ERROR;
}
if (s2n_connection_set_recv_ctx(conn->s2n_conn,
callbacks->io_context)) {
on_s2n_error(conn, s2n_connection_set_recv_ctx);
return SLURM_ERROR;
}
if (s2n_connection_set_send_cb(conn->s2n_conn, callbacks->send)) {
on_s2n_error(conn, s2n_connection_set_send_cb);
return SLURM_ERROR;
}
if (s2n_connection_set_send_ctx(conn->s2n_conn,
callbacks->io_context)) {
on_s2n_error(conn, s2n_connection_set_send_ctx);
return SLURM_ERROR;
}
log_flag(TLS, "Successfully set recv_cb:%p recv_ctx:%p send_cb:%p send_ctx:%p on s2n conn %p",
callbacks->recv, callbacks->io_context,
callbacks->send, callbacks->io_context, conn->s2n_conn);
return SLURM_SUCCESS;
}
extern void tls_p_set_graceful_shutdown(tls_conn_t *conn,
bool do_graceful_shutdown)
{
xassert(conn);
log_flag(TLS, "%s: %s graceful shutdown on fd:%d->%d",
plugin_type, do_graceful_shutdown ? "Enabled" : "Disabled",
conn->input_fd, conn->output_fd);
conn->do_graceful_shutdown = do_graceful_shutdown;
}