blob: 45f9d5bb8e4b196ca2f6dee426f1513a91574a95 [file] [log] [blame]
/*****************************************************************************\
* certmgr_script.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 "slurm/slurm.h"
#include "slurm/slurm_errno.h"
#include "src/common/slurm_xlator.h"
#include "src/common/log.h"
#include "src/common/node_conf.h"
#include "src/common/run_command.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/interfaces/certmgr.h"
const char plugin_name[] = "Certificate manager script plugin";
const char plugin_type[] = "certmgr/script";
const uint32_t plugin_version = SLURM_VERSION_NUMBER;
typedef enum {
GEN_CSR,
GET_CERT_KEY,
GET_TOKEN,
SIGN_CSR,
VALID_NODE,
} cert_script_type_t;
typedef struct {
char *key;
char *path;
bool required;
} cert_script_t;
cert_script_t cert_scripts[] = {
[GEN_CSR] = {
.key = "generate_csr_script=",
.required = true,
},
[GET_CERT_KEY] = {
.key = "get_node_cert_key_script=",
.required = true,
},
[GET_TOKEN] = {
.key = "get_node_token_script=",
.required = true,
},
[SIGN_CSR] = {
.key = "sign_csr_script=",
.required = true,
},
[VALID_NODE] = {
.key = "validate_node_script=",
.required = false,
},
};
static int _set_script_path(cert_script_t *script)
{
script->path = conf_get_opt_str(slurm_conf.certmgr_params, script->key);
if (!script->path && script->required) {
error("No script was set with '%s' in CertmgrParameters setting",
script->key);
return SLURM_ERROR;
}
return SLURM_SUCCESS;
}
static int _load_script_paths(void)
{
if (running_in_slurmctld()) {
/* needs to validate nodes and sign CSR's */
if (_set_script_path(&cert_scripts[SIGN_CSR]))
return SLURM_ERROR;
if (_set_script_path(&cert_scripts[VALID_NODE]))
return SLURM_ERROR;
return SLURM_SUCCESS;
}
if (running_in_daemon()) {
/* needs resources to get a signed certificate from slurmctld */
if (_set_script_path(&cert_scripts[GEN_CSR]))
return SLURM_ERROR;
if (_set_script_path(&cert_scripts[GET_CERT_KEY]))
return SLURM_ERROR;
if (_set_script_path(&cert_scripts[GET_TOKEN]))
return SLURM_ERROR;
return SLURM_SUCCESS;
}
return SLURM_ERROR;
}
static char *_run_script(cert_script_type_t cert_script_type,
char **script_argv, int *rc_ptr)
{
int status = SLURM_ERROR;
bool timed_out = false;
char *output = NULL;
char *script_path = NULL;
xassert(rc_ptr);
run_command_args_t run_command_args = {
.max_wait = 5000,
.script_argv = script_argv,
.status = rc_ptr,
.timed_out = &timed_out,
};
script_path = cert_scripts[cert_script_type].path;
run_command_args.script_path = script_path;
if (script_argv)
script_argv[0] = script_path;
output = run_command(&run_command_args);
if (timed_out) {
error("%s: Timed out running script '%s'",
plugin_type, run_command_args.script_path);
goto fail;
}
if (*rc_ptr) {
error("%s: Error code %d encountered while running script '%s'. stdout+stderr from script:\n%s",
plugin_type, status, run_command_args.script_path,
output);
goto fail;
}
return output;
fail:
xfree(output);
return NULL;
}
extern char *certmgr_p_get_node_cert_key(char *node_name)
{
char **script_argv;
int script_rc;
char *key = NULL;
script_argv = xcalloc(3, sizeof(char *)); /* NULL terminated */
/* script_argv[0] set to script path later */
script_argv[1] = node_name;
key = _run_script(GET_CERT_KEY, script_argv, &script_rc);
xfree(script_argv);
if (script_rc) {
error("%s: Unable to get node's private certificate key.", plugin_type);
goto fail;
} else if (!key || !*key) {
error("%s: Unable to get node's private certificate key. Script printed nothing to stdout",
plugin_type);
goto fail;
} else {
log_flag(AUDIT_TLS, "Successfully retrieved node's private certificate key");
}
return key;
fail:
xfree(key);
return NULL;
}
extern int init(void)
{
debug("loaded");
if (_load_script_paths())
return SLURM_ERROR;
return SLURM_SUCCESS;
}
extern void fini(void)
{
return;
}
extern char *certmgr_p_get_node_token(char *node_name)
{
char **script_argv;
int script_rc;
char *token = NULL;
script_argv = xcalloc(3, sizeof(char *)); /* NULL terminated */
/* script_argv[0] set to script path later */
script_argv[1] = node_name;
token = _run_script(GET_TOKEN, script_argv, &script_rc);
xfree(script_argv);
if (script_rc) {
error("%s: Unable to get node's unique token.", plugin_type);
goto fail;
} else if (!token || !*token) {
error("%s: Unable to get node's unique token. Script printed nothing to stdout",
plugin_type);
goto fail;
} else {
log_flag(AUDIT_TLS, "Successfully retrieved unique node token");
}
return token;
fail:
xfree(token);
return NULL;
}
extern char *certmgr_p_generate_csr(char *node_name)
{
char **script_argv;
int script_rc;
char *csr = NULL;
script_argv = xcalloc(3, sizeof(char *)); /* NULL terminated */
/* script_argv[0] set to script path later */
script_argv[1] = node_name;
csr = _run_script(GEN_CSR, script_argv, &script_rc);
xfree(script_argv);
if (script_rc) {
error("%s: Unable to generate node certificate signing request",
plugin_type);
goto fail;
} else if (!csr || !*csr) {
error("%s: Unable to generate node certificate signing request. Script printed nothing to stdout",
plugin_type);
goto fail;
} else {
log_flag(AUDIT_TLS, "Successfully generated csr: \n%s", csr);
}
return csr;
fail:
xfree(csr);
return NULL;
}
extern char *certmgr_p_sign_csr(char *csr, bool is_client_auth, char *token,
char *name)
{
char **script_argv;
int script_rc = SLURM_ERROR;
char *signed_cert_pem = NULL;
char *output = NULL;
node_record_t *node = NULL;
if (!name) {
error("%s: No name given, cannot sign CSR.",
plugin_type);
return NULL;
}
if (!(node = find_node_record(name))) {
log_flag(AUDIT_TLS, "Could not find node record for '%s'.", name);
}
if (is_client_auth) {
log_flag(AUDIT_TLS, "Client '%s' connected via mTLS, skipping validation.", name);
goto skip_validation_script;
}
if (node && node->cert_token) {
if (xstrcmp(node->cert_token, token)) {
error("%s: Token does not match what was set in node record table for node '%s'.",
plugin_type, name);
return NULL;
}
log_flag(AUDIT_TLS, "Token received from node '%s' matches what was set in node record table.",
name);
goto skip_validation_script;
}
if (!cert_scripts[VALID_NODE].path) {
log_flag(AUDIT_TLS, "No token set in node record table for node '%s', and no validation script is configured. Token is invalid.",
name);
return NULL;
}
if (node) {
log_flag(AUDIT_TLS, "No token set in node record table for node '%s'. Will run validation script to check token.",
name);
} else {
log_flag(AUDIT_TLS, "Running validation script to check token for '%s'.",
name);
}
script_argv = xcalloc(4, sizeof(char *)); /* NULL terminated */
/* script_argv[0] set to script path later */
script_argv[1] = name;
script_argv[2] = token;
output = _run_script(VALID_NODE, script_argv, &script_rc);
xfree(output);
xfree(script_argv);
if (script_rc) {
error("%s: Unable to validate node certificate signing request for node '%s'.",
plugin_type, name);
return NULL;
}
skip_validation_script:
log_flag(AUDIT_TLS, "Successfully validated node token for node %s.",
name);
script_argv = xcalloc(3, sizeof(char *));
/* script_argv[0] set to script path later */
script_argv[1] = csr;
signed_cert_pem = _run_script(SIGN_CSR, script_argv, &script_rc);
xfree(script_argv);
if (script_rc) {
error("%s: Unable to sign node certificate signing request for node '%s'.",
plugin_type, name);
goto fail;
} else if (!signed_cert_pem || !*signed_cert_pem) {
error("%s: Unable to sign node certificate signing request for node '%s'. Script printed nothing to stdout",
plugin_type, name);
goto fail;
} else {
log_flag(AUDIT_TLS, "Successfully generated signed certificate for node '%s': \n%s",
name, signed_cert_pem);
}
if ((xstrstr(slurm_conf.certmgr_params, "single_use_tokens")) && node &&
node->cert_token) {
xfree(node->cert_token);
log_flag(AUDIT_TLS, "Token for node '%s' has been reset following successful certificate signing.",
node->name);
}
return signed_cert_pem;
fail:
xfree(signed_cert_pem);
return NULL;
}