blob: d8cc258f3710f829590f86398ba8b71a390120e3 [file] [log] [blame]
/*****************************************************************************\
* src/common/slurm_cred.c - SLURM job credential functions
* $Id$
*****************************************************************************
* Copyright (C) 2002-2006 The Regents of the University of California.
* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
* Written by Mark A. Grondona <mgrondona@llnl.gov>.
* UCRL-CODE-226842.
*
* This file is part of SLURM, a resource management program.
* For details, see <http://www.llnl.gov/linux/slurm/>.
*
* 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.
\*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <slurm/slurm_errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/time.h>
/*
* OpenSSL includes
*/
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#if WITH_PTHREADS
# include <pthread.h>
#endif /* WITH_PTHREADS */
#include "src/common/macros.h"
#include "src/common/list.h"
#include "src/common/log.h"
#include "src/common/xmalloc.h"
#include "src/common/xassert.h"
#include "src/common/xstring.h"
#include "src/common/io_hdr.h"
#include "src/common/slurm_cred.h"
/*
* Default credential information expiration window:
*/
#define DEFAULT_EXPIRATION_WINDOW 600
#define MAX_TIME 0x7fffffff
/*
* slurm job credential state
*
*/
typedef struct {
uint32_t jobid; /* SLURM job id for this credential */
uint32_t stepid; /* SLURM step id for this credential */
time_t expiration; /* Time at which cred is no longer good */
} cred_state_t;
/*
* slurm job state information
* tracks jobids for which all future credentials have been revoked
*
*/
typedef struct {
uint32_t jobid;
time_t revoked; /* Time at which credentials were revoked */
time_t ctime; /* Time that this entry was created */
time_t expiration; /* Time at which credentials can be purged */
} job_state_t;
/*
* Completion of slurm credential context
*/
enum ctx_type {
SLURM_CRED_CREATOR,
SLURM_CRED_VERIFIER
};
struct slurm_cred_context {
#ifndef NDEBUG
# define CRED_CTX_MAGIC 0x0c0c0c
int magic;
#endif
#if WITH_PTHREADS
pthread_mutex_t mutex;
#endif
enum ctx_type type; /* type of context (creator or verifier) */
EVP_PKEY *key; /* private or public key */
List job_list; /* List of used jobids (for verifier) */
List state_list; /* List of cred states (for verifier) */
int expiry_window; /* expiration window for cached creds */
EVP_PKEY *exkey; /* Old public key if key is updated */
time_t exkey_exp; /* Old key expiration time */
};
/*
* Completion of slurm job credential type:
*
*/
struct slurm_job_credential {
#ifndef NDEBUG
# define CRED_MAGIC 0x0b0b0b
int magic;
#endif
#ifdef WITH_PTHREADS
pthread_mutex_t mutex;
#endif
uint32_t jobid; /* Job ID associated with this credential */
uint32_t stepid; /* Job step ID for this credential */
uid_t uid; /* user for which this cred is valid */
time_t ctime; /* time of credential creation */
char *nodes; /* list of hostnames for which the cred is ok*/
uint32_t alloc_lps_cnt; /* Number of hosts in the list above */
uint32_t *alloc_lps; /* Number of tasks on each host */
unsigned char *signature; /* credential signature */
unsigned int siglen; /* signature length in bytes */
};
/*
* Static prototypes:
*/
static slurm_cred_ctx_t _slurm_cred_ctx_alloc(void);
static slurm_cred_t _slurm_cred_alloc(void);
static int _ctx_update_private_key(slurm_cred_ctx_t ctx, const char *path);
static int _ctx_update_public_key(slurm_cred_ctx_t ctx, const char *path);
static bool _exkey_is_valid(slurm_cred_ctx_t ctx);
static cred_state_t * _cred_state_create(slurm_cred_ctx_t ctx, slurm_cred_t c);
static job_state_t * _job_state_create(uint32_t jobid);
static void _cred_state_destroy(cred_state_t *cs);
static void _job_state_destroy(job_state_t *js);
static job_state_t * _find_job_state(slurm_cred_ctx_t ctx, uint32_t jobid);
static job_state_t * _insert_job_state(slurm_cred_ctx_t ctx, uint32_t jobid);
static int _find_cred_state(cred_state_t *c, slurm_cred_t cred);
static void _insert_cred_state(slurm_cred_ctx_t ctx, slurm_cred_t cred);
static void _clear_expired_job_states(slurm_cred_ctx_t ctx);
static void _clear_expired_credential_states(slurm_cred_ctx_t ctx);
static void _verifier_ctx_init(slurm_cred_ctx_t ctx);
static bool _credential_replayed(slurm_cred_ctx_t ctx, slurm_cred_t cred);
static bool _credential_revoked(slurm_cred_ctx_t ctx, slurm_cred_t cred);
static EVP_PKEY * _read_private_key(const char *path);
static EVP_PKEY * _read_public_key(const char *path);
static int _slurm_cred_sign(slurm_cred_ctx_t ctx, slurm_cred_t cred);
static int _slurm_cred_verify_signature(slurm_cred_ctx_t ctx, slurm_cred_t c);
static job_state_t * _job_state_unpack_one(Buf buffer);
static cred_state_t * _cred_state_unpack_one(Buf buffer);
static void _pack_cred(slurm_cred_t cred, Buf buffer);
static void _job_state_unpack(slurm_cred_ctx_t ctx, Buf buffer);
static void _job_state_pack(slurm_cred_ctx_t ctx, Buf buffer);
static void _cred_state_unpack(slurm_cred_ctx_t ctx, Buf buffer);
static void _cred_state_pack(slurm_cred_ctx_t ctx, Buf buffer);
static void _job_state_pack_one(job_state_t *j, Buf buffer);
static void _cred_state_pack_one(cred_state_t *s, Buf buffer);
#ifndef DISABLE_LOCALTIME
static char * timestr (const time_t *tp, char *buf, size_t n);
#endif
slurm_cred_ctx_t
slurm_cred_creator_ctx_create(const char *path)
{
slurm_cred_ctx_t ctx = NULL;
xassert(path != NULL);
ctx = _slurm_cred_ctx_alloc();
slurm_mutex_lock(&ctx->mutex);
ctx->type = SLURM_CRED_CREATOR;
if (!(ctx->key = _read_private_key(path)))
goto fail;
slurm_mutex_unlock(&ctx->mutex);
return ctx;
fail:
slurm_mutex_unlock(&ctx->mutex);
slurm_cred_ctx_destroy(ctx);
return NULL;
}
slurm_cred_ctx_t
slurm_cred_verifier_ctx_create(const char *path)
{
slurm_cred_ctx_t ctx = NULL;
xassert(path != NULL);
ctx = _slurm_cred_ctx_alloc();
slurm_mutex_lock(&ctx->mutex);
ctx->type = SLURM_CRED_VERIFIER;
if (!(ctx->key = _read_public_key(path)))
goto fail;
_verifier_ctx_init(ctx);
slurm_mutex_unlock(&ctx->mutex);
return ctx;
fail:
slurm_mutex_unlock(&ctx->mutex);
slurm_cred_ctx_destroy(ctx);
return NULL;
}
void
slurm_cred_ctx_destroy(slurm_cred_ctx_t ctx)
{
if (ctx == NULL)
return;
slurm_mutex_lock(&ctx->mutex);
xassert(ctx->magic == CRED_CTX_MAGIC);
if (ctx->key)
EVP_PKEY_free(ctx->key);
if (ctx->job_list)
list_destroy(ctx->job_list);
if (ctx->state_list)
list_destroy(ctx->state_list);
xassert(ctx->magic = ~CRED_CTX_MAGIC);
slurm_mutex_unlock(&ctx->mutex);
slurm_mutex_destroy(&ctx->mutex);
xfree(ctx);
return;
}
int
slurm_cred_ctx_set(slurm_cred_ctx_t ctx, slurm_cred_opt_t opt, ...)
{
int rc = SLURM_SUCCESS;
va_list ap;
xassert(ctx != NULL);
va_start(ap, opt);
slurm_mutex_lock(&ctx->mutex);
xassert(ctx->magic == CRED_CTX_MAGIC);
switch (opt) {
case SLURM_CRED_OPT_EXPIRY_WINDOW:
ctx->expiry_window = va_arg(ap, int);
break;
default:
slurm_seterrno(EINVAL);
rc = SLURM_ERROR;
break;
}
slurm_mutex_unlock(&ctx->mutex);
va_end(ap);
return rc;
}
int
slurm_cred_ctx_get(slurm_cred_ctx_t ctx, slurm_cred_opt_t opt, ...)
{
int rc = SLURM_SUCCESS;
va_list ap;
int *intp;
xassert(ctx != NULL);
va_start(ap, opt);
slurm_mutex_lock(&ctx->mutex);
xassert(ctx->magic == CRED_CTX_MAGIC);
switch (opt) {
case SLURM_CRED_OPT_EXPIRY_WINDOW:
intp = va_arg(ap, int *);
*intp = ctx->expiry_window;
break;
default:
slurm_seterrno(EINVAL);
rc = SLURM_ERROR;
break;
}
slurm_mutex_unlock(&ctx->mutex);
va_end(ap);
return rc;
}
int
slurm_cred_ctx_key_update(slurm_cred_ctx_t ctx, const char *path)
{
if (ctx->type == SLURM_CRED_CREATOR)
return _ctx_update_private_key(ctx, path);
else
return _ctx_update_public_key(ctx, path);
}
slurm_cred_t
slurm_cred_create(slurm_cred_ctx_t ctx, slurm_cred_arg_t *arg)
{
slurm_cred_t cred = NULL;
xassert(ctx != NULL);
xassert(arg != NULL);
slurm_mutex_lock(&ctx->mutex);
xassert(ctx->magic == CRED_CTX_MAGIC);
xassert(ctx->type == SLURM_CRED_CREATOR);
cred = _slurm_cred_alloc();
xassert(cred != NULL);
slurm_mutex_lock(&cred->mutex);
xassert(cred->magic == CRED_MAGIC);
cred->jobid = arg->jobid;
cred->stepid = arg->stepid;
cred->uid = arg->uid;
cred->nodes = xstrdup(arg->hostlist);
cred->alloc_lps_cnt = arg->alloc_lps_cnt;
cred->alloc_lps = NULL;
if (cred->alloc_lps_cnt > 0) {
cred->alloc_lps = xmalloc(cred->alloc_lps_cnt * sizeof(uint32_t));
memcpy(cred->alloc_lps, arg->alloc_lps, cred->alloc_lps_cnt * sizeof(uint32_t));
}
cred->ctime = time(NULL);
if (_slurm_cred_sign(ctx, cred) < 0)
goto fail;
slurm_mutex_unlock(&ctx->mutex);
slurm_mutex_unlock(&cred->mutex);
return cred;
fail:
slurm_mutex_unlock(&ctx->mutex);
slurm_mutex_unlock(&cred->mutex);
slurm_cred_destroy(cred);
return NULL;
}
slurm_cred_t
slurm_cred_copy(slurm_cred_t cred)
{
slurm_cred_t rcred = NULL;
xassert(cred != NULL);
slurm_mutex_lock(&cred->mutex);
rcred = _slurm_cred_alloc();
xassert(rcred != NULL);
slurm_mutex_lock(&rcred->mutex);
xassert(rcred->magic == CRED_MAGIC);
rcred->jobid = cred->jobid;
rcred->stepid = cred->stepid;
rcred->uid = cred->uid;
rcred->nodes = xstrdup(cred->nodes);
rcred->alloc_lps_cnt = cred->alloc_lps_cnt;
rcred->alloc_lps = NULL;
if (rcred->alloc_lps_cnt > 0) {
rcred->alloc_lps = xmalloc(rcred->alloc_lps_cnt * sizeof(uint32_t));
memcpy(rcred->alloc_lps, cred->alloc_lps,
rcred->alloc_lps_cnt * sizeof(uint32_t));
}
rcred->ctime = cred->ctime;
rcred->signature = (unsigned char *)xstrdup((char *)cred->signature);
slurm_mutex_unlock(&cred->mutex);
slurm_mutex_unlock(&rcred->mutex);
return rcred;
}
slurm_cred_t
slurm_cred_faker(slurm_cred_arg_t *arg)
{
int fd;
slurm_cred_t cred = NULL;
xassert(arg != NULL);
cred = _slurm_cred_alloc();
slurm_mutex_lock(&cred->mutex);
cred->jobid = arg->jobid;
cred->stepid = arg->stepid;
cred->uid = arg->uid;
cred->nodes = xstrdup(arg->hostlist);
cred->alloc_lps_cnt = arg->alloc_lps_cnt;
cred->alloc_lps = NULL;
if (cred->alloc_lps_cnt > 0) {
cred->alloc_lps = xmalloc(cred->alloc_lps_cnt * sizeof(uint32_t));
memcpy(cred->alloc_lps, arg->alloc_lps, cred->alloc_lps_cnt * sizeof(uint32_t));
}
cred->ctime = time(NULL);
cred->siglen = SLURM_IO_KEY_SIZE;
cred->signature = xmalloc(cred->siglen * sizeof(char));
if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) {
if (read(fd, cred->signature, cred->siglen) == -1)
error("reading fake signature from /dev/urandom: %m");
if (close(fd) < 0)
error("close(/dev/urandom): %m");
} else { /* Note: some systems lack this file */
unsigned int i;
struct timeval tv;
gettimeofday(&tv, NULL);
i = (unsigned int) (tv.tv_sec + tv.tv_usec);
srand((unsigned int) i);
for (i=0; i<cred->siglen; i++)
cred->signature[i] = (rand() & 0xff);
}
slurm_mutex_unlock(&cred->mutex);
return cred;
}
int
slurm_cred_verify(slurm_cred_ctx_t ctx, slurm_cred_t cred,
slurm_cred_arg_t *arg)
{
time_t now = time(NULL);
int errnum;
xassert(ctx != NULL);
xassert(cred != NULL);
xassert(arg != NULL);
slurm_mutex_lock(&ctx->mutex);
slurm_mutex_lock(&cred->mutex);
xassert(ctx->magic == CRED_CTX_MAGIC);
xassert(ctx->type == SLURM_CRED_VERIFIER);
xassert(cred->magic == CRED_MAGIC);
if (_slurm_cred_verify_signature(ctx, cred) < 0) {
slurm_seterrno(ESLURMD_INVALID_JOB_CREDENTIAL);
goto error;
}
if (now > (cred->ctime + ctx->expiry_window)) {
slurm_seterrno(ESLURMD_CREDENTIAL_EXPIRED);
goto error;
}
slurm_cred_handle_reissue(ctx, cred);
if (_credential_revoked(ctx, cred)) {
slurm_seterrno(ESLURMD_CREDENTIAL_REVOKED);
goto error;
}
if (_credential_replayed(ctx, cred)) {
slurm_seterrno(ESLURMD_CREDENTIAL_REPLAYED);
goto error;
}
slurm_mutex_unlock(&ctx->mutex);
/*
* set arguments to cred contents
*/
arg->jobid = cred->jobid;
arg->stepid = cred->stepid;
arg->uid = cred->uid;
arg->hostlist = xstrdup(cred->nodes);
arg->alloc_lps_cnt = cred->alloc_lps_cnt;
arg->alloc_lps = NULL;
if (arg->alloc_lps_cnt > 0) {
arg->alloc_lps = xmalloc(arg->alloc_lps_cnt * sizeof(uint32_t));
memcpy(arg->alloc_lps, cred->alloc_lps, arg->alloc_lps_cnt * sizeof(uint32_t));
}
slurm_mutex_unlock(&cred->mutex);
return SLURM_SUCCESS;
error:
errnum = slurm_get_errno();
slurm_mutex_unlock(&ctx->mutex);
slurm_mutex_unlock(&cred->mutex);
slurm_seterrno(errnum);
return SLURM_ERROR;
}
void
slurm_cred_destroy(slurm_cred_t cred)
{
if (cred == NULL)
return;
xassert(cred->magic == CRED_MAGIC);
slurm_mutex_lock(&cred->mutex);
xfree(cred->nodes);
xfree(cred->alloc_lps);
xfree(cred->signature);
xassert(cred->magic = ~CRED_MAGIC);
slurm_mutex_unlock(&cred->mutex);
slurm_mutex_destroy(&cred->mutex);
xfree(cred);
}
bool
slurm_cred_jobid_cached(slurm_cred_ctx_t ctx, uint32_t jobid)
{
bool retval = false;
xassert(ctx != NULL);
xassert(ctx->magic == CRED_CTX_MAGIC);
xassert(ctx->type == SLURM_CRED_VERIFIER);
slurm_mutex_lock(&ctx->mutex);
_clear_expired_job_states(ctx);
/*
* Return true if we find a cached job state for job id `jobid'
*/
retval = (_find_job_state(ctx, jobid) != NULL);
slurm_mutex_unlock(&ctx->mutex);
return retval;
}
int
slurm_cred_insert_jobid(slurm_cred_ctx_t ctx, uint32_t jobid)
{
xassert(ctx != NULL);
xassert(ctx->magic == CRED_CTX_MAGIC);
xassert(ctx->type == SLURM_CRED_VERIFIER);
slurm_mutex_lock(&ctx->mutex);
_clear_expired_job_states(ctx);
(void) _insert_job_state(ctx, jobid);
slurm_mutex_unlock(&ctx->mutex);
return SLURM_SUCCESS;
}
int
slurm_cred_rewind(slurm_cred_ctx_t ctx, slurm_cred_t cred)
{
int rc = 0;
xassert(ctx != NULL);
slurm_mutex_lock(&ctx->mutex);
xassert(ctx->magic == CRED_CTX_MAGIC);
xassert(ctx->type == SLURM_CRED_VERIFIER);
rc = list_delete_all(ctx->state_list, (ListFindF) _find_cred_state, cred);
slurm_mutex_unlock(&ctx->mutex);
return (rc > 0 ? SLURM_SUCCESS : SLURM_FAILURE);
}
int
slurm_cred_revoke(slurm_cred_ctx_t ctx, uint32_t jobid, time_t time)
{
job_state_t *j = NULL;
xassert(ctx != NULL);
slurm_mutex_lock(&ctx->mutex);
xassert(ctx->magic == CRED_CTX_MAGIC);
xassert(ctx->type == SLURM_CRED_VERIFIER);
_clear_expired_job_states(ctx);
if (!(j = _find_job_state(ctx, jobid))) {
/*
* This node has not yet seen a job step for this
* job. Insert a job state object so that we can
* revoke any future credentials.
*/
j = _insert_job_state(ctx, jobid);
}
if (j->revoked) {
slurm_seterrno(EEXIST);
goto error;
}
j->revoked = time;
slurm_mutex_unlock(&ctx->mutex);
return SLURM_SUCCESS;
error:
slurm_mutex_unlock(&ctx->mutex);
return SLURM_FAILURE;
}
int
slurm_cred_begin_expiration(slurm_cred_ctx_t ctx, uint32_t jobid)
{
char buf[64];
job_state_t *j = NULL;
xassert(ctx != NULL);
slurm_mutex_lock(&ctx->mutex);
xassert(ctx->magic == CRED_CTX_MAGIC);
xassert(ctx->type == SLURM_CRED_VERIFIER);
_clear_expired_job_states(ctx);
if (!(j = _find_job_state(ctx, jobid))) {
slurm_seterrno(ESRCH);
goto error;
}
if (j->expiration < (time_t) MAX_TIME) {
slurm_seterrno(EEXIST);
goto error;
}
j->expiration = time(NULL) + ctx->expiry_window;
debug2 ("set revoke expiration for jobid %u to %s",
j->jobid, timestr (&j->expiration, buf, 64) );
slurm_mutex_unlock(&ctx->mutex);
return SLURM_SUCCESS;
error:
slurm_mutex_unlock(&ctx->mutex);
return SLURM_ERROR;
}
int
slurm_cred_get_signature(slurm_cred_t cred, char **datap, int *datalen)
{
xassert(cred != NULL);
xassert(datap != NULL);
xassert(datalen != NULL);
slurm_mutex_lock(&cred->mutex);
*datap = (char *) cred->signature;
*datalen = cred->siglen;
slurm_mutex_unlock(&cred->mutex);
return SLURM_SUCCESS;
}
void
slurm_cred_pack(slurm_cred_t cred, Buf buffer)
{
xassert(cred != NULL);
xassert(cred->magic == CRED_MAGIC);
slurm_mutex_lock(&cred->mutex);
_pack_cred(cred, buffer);
xassert(cred->siglen > 0);
packmem((char *) cred->signature, (uint16_t) cred->siglen, buffer);
slurm_mutex_unlock(&cred->mutex);
return;
}
slurm_cred_t
slurm_cred_unpack(Buf buffer)
{
uint16_t len;
uint32_t tmpint;
slurm_cred_t cred = NULL;
char **sigp;
xassert(buffer != NULL);
cred = _slurm_cred_alloc();
slurm_mutex_lock(&cred->mutex);
sigp = (char **) &cred->signature;
safe_unpack32( &cred->jobid, buffer);
safe_unpack32( &cred->stepid, buffer);
safe_unpack32( &tmpint, buffer);
cred->uid = tmpint;
safe_unpackstr_xmalloc( &cred->nodes, &len, buffer);
safe_unpack32( &cred->alloc_lps_cnt, buffer);
if (cred->alloc_lps_cnt > 0)
safe_unpack32_array(&cred->alloc_lps, &tmpint, buffer);
safe_unpack_time( &cred->ctime, buffer);
safe_unpackmem_xmalloc( sigp, &len, buffer);
xassert(len > 0);
cred->siglen = len;
slurm_mutex_unlock(&cred->mutex);
return cred;
unpack_error:
slurm_mutex_unlock(&cred->mutex);
slurm_cred_destroy(cred);
return NULL;
}
int
slurm_cred_ctx_pack(slurm_cred_ctx_t ctx, Buf buffer)
{
slurm_mutex_lock(&ctx->mutex);
_job_state_pack(ctx, buffer);
_cred_state_pack(ctx, buffer);
slurm_mutex_unlock(&ctx->mutex);
return SLURM_SUCCESS;
}
int
slurm_cred_ctx_unpack(slurm_cred_ctx_t ctx, Buf buffer)
{
xassert(ctx != NULL);
xassert(ctx->magic == CRED_CTX_MAGIC);
xassert(ctx->type == SLURM_CRED_VERIFIER);
slurm_mutex_lock(&ctx->mutex);
/*
* Unpack job state list and cred state list from buffer
* appening them onto ctx->state_list and ctx->job_list.
*/
_job_state_unpack(ctx, buffer);
_cred_state_unpack(ctx, buffer);
slurm_mutex_unlock(&ctx->mutex);
return SLURM_SUCCESS;
}
void
slurm_cred_print(slurm_cred_t cred)
{
int i;
if (cred == NULL)
return;
slurm_mutex_lock(&cred->mutex);
xassert(cred->magic == CRED_MAGIC);
info("Cred: Jobid %u", cred->jobid );
info("Cred: Stepid %u", cred->jobid );
info("Cred: UID %lu", (u_long) cred->uid );
info("Cred: Nodes %s", cred->nodes );
info("Cred: alloc_lps_cnt %d", cred->alloc_lps_cnt );
info("Cred: alloc_lps: ");
for (i=0; i<cred->alloc_lps_cnt; i++)
info("alloc_lps[%d] = %u ", i, cred->alloc_lps[i]);
info("Cred: ctime %s", ctime(&cred->ctime) );
info("Cred: siglen %u", cred->siglen );
slurm_mutex_unlock(&cred->mutex);
}
static EVP_PKEY *
_read_private_key(const char *path)
{
FILE *fp = NULL;
EVP_PKEY *pk = NULL;
xassert(path != NULL);
if (!(fp = fopen(path, "r"))) {
error ("can't open key file '%s' : %m", path);
return NULL;
}
if (!PEM_read_PrivateKey(fp, &pk, NULL, NULL))
error ("PEM_read_PrivateKey [%s]: %m", path);
fclose(fp);
return pk;
}
static EVP_PKEY *
_read_public_key(const char *path)
{
FILE *fp = NULL;
EVP_PKEY *pk = NULL;
xassert(path != NULL);
if ((fp = fopen(path, "r")) == NULL) {
error ("can't open public key '%s' : %m ", path);
return NULL;
}
if (!PEM_read_PUBKEY(fp, &pk, NULL, NULL))
error("PEM_read_PUBKEY[%s]: %m", path);
fclose(fp);
return pk;
}
static void
_verifier_ctx_init(slurm_cred_ctx_t ctx)
{
xassert(ctx != NULL);
xassert(ctx->magic == CRED_CTX_MAGIC);
xassert(ctx->type == SLURM_CRED_VERIFIER);
ctx->job_list = list_create((ListDelF) _job_state_destroy);
ctx->state_list = list_create((ListDelF) _cred_state_destroy);
return;
}
static int
_ctx_update_private_key(slurm_cred_ctx_t ctx, const char *path)
{
EVP_PKEY *pk = NULL;
EVP_PKEY *tmpk = NULL;
xassert(ctx != NULL);
if (!(pk = _read_private_key(path)))
return SLURM_ERROR;
slurm_mutex_lock(&ctx->mutex);
xassert(ctx->magic == CRED_CTX_MAGIC);
xassert(ctx->type == SLURM_CRED_CREATOR);
tmpk = ctx->key;
ctx->key = pk;
slurm_mutex_unlock(&ctx->mutex);
EVP_PKEY_free(tmpk);
return SLURM_SUCCESS;
}
static int
_ctx_update_public_key(slurm_cred_ctx_t ctx, const char *path)
{
EVP_PKEY *pk = NULL;
xassert(ctx != NULL);
if (!(pk = _read_public_key(path)))
return SLURM_ERROR;
slurm_mutex_lock(&ctx->mutex);
xassert(ctx->magic == CRED_CTX_MAGIC);
xassert(ctx->type == SLURM_CRED_VERIFIER);
if (ctx->exkey)
EVP_PKEY_free(ctx->exkey);
ctx->exkey = ctx->key;
ctx->key = pk;
/*
* exkey expires in expiry_window seconds plus one minute.
* This should be long enough to capture any keys in-flight.
*/
ctx->exkey_exp = time(NULL) + ctx->expiry_window + 60;
slurm_mutex_unlock(&ctx->mutex);
return SLURM_SUCCESS;
}
static bool
_exkey_is_valid(slurm_cred_ctx_t ctx)
{
if (!ctx->exkey) return false;
if (time(NULL) > ctx->exkey_exp) {
debug2("old job credential key slurmd expired");
EVP_PKEY_free(ctx->exkey);
ctx->exkey = NULL;
return false;
}
return true;
}
static slurm_cred_ctx_t
_slurm_cred_ctx_alloc(void)
{
slurm_cred_ctx_t ctx = xmalloc(sizeof(*ctx));
slurm_mutex_init(&ctx->mutex);
slurm_mutex_lock(&ctx->mutex);
ctx->key = NULL;
ctx->job_list = NULL;
ctx->state_list = NULL;
ctx->expiry_window = DEFAULT_EXPIRATION_WINDOW;
ctx->exkey = NULL;
ctx->exkey_exp = (time_t) -1;
xassert(ctx->magic = CRED_CTX_MAGIC);
slurm_mutex_unlock(&ctx->mutex);
return ctx;
}
static slurm_cred_t
_slurm_cred_alloc(void)
{
slurm_cred_t cred = xmalloc(sizeof(*cred));
slurm_mutex_init(&cred->mutex);
cred->jobid = 0;
cred->stepid = 0;
cred->uid = (uid_t) -1;
cred->nodes = NULL;
cred->alloc_lps_cnt = 0;
cred->alloc_lps = NULL;
cred->signature = NULL;
cred->siglen = 0;
xassert(cred->magic = CRED_MAGIC);
return cred;
}
static const char *
_ssl_error(void)
{
return ERR_reason_error_string(ERR_get_error());
}
#ifdef EXTREME_DEBUG
static void
_print_data(char *data, int datalen)
{
char buf[1024];
size_t len = 0;
int i;
for (i = 0; i < datalen; i += sizeof(char))
len += sprintf(buf+len, "%02x", data[i]);
}
#endif
static int
_slurm_cred_sign(slurm_cred_ctx_t ctx, slurm_cred_t cred)
{
EVP_MD_CTX ectx;
Buf buffer;
int rc = SLURM_SUCCESS;
unsigned int *lenp = &cred->siglen;
int ksize = EVP_PKEY_size(ctx->key);
/*
* Allocate memory for signature: at most EVP_PKEY_size() bytes
*/
cred->signature = xmalloc(ksize * sizeof(unsigned char));
buffer = init_buf(4096);
_pack_cred(cred, buffer);
EVP_SignInit(&ectx, EVP_sha1());
EVP_SignUpdate(&ectx, get_buf_data(buffer), get_buf_offset(buffer));
if (!(EVP_SignFinal(&ectx, cred->signature, lenp, ctx->key))) {
ERR_print_errors_fp(log_fp());
rc = SLURM_ERROR;
}
#ifdef HAVE_EVP_MD_CTX_CLEANUP
/* Note: Likely memory leak if this function is absent */
EVP_MD_CTX_cleanup(&ectx);
#endif
free_buf(buffer);
return rc;
}
static int
_slurm_cred_verify_signature(slurm_cred_ctx_t ctx, slurm_cred_t cred)
{
EVP_MD_CTX ectx;
Buf buffer;
int rc;
unsigned char *sig = cred->signature;
int siglen = cred->siglen;
buffer = init_buf(4096);
_pack_cred(cred, buffer);
debug("Checking credential with %d bytes of sig data", siglen);
EVP_VerifyInit(&ectx, EVP_sha1());
EVP_VerifyUpdate(&ectx, get_buf_data(buffer), get_buf_offset(buffer));
if (!(rc = EVP_VerifyFinal(&ectx, sig, siglen, ctx->key))) {
/*
* Check against old key if one exists and is valid
*/
if (_exkey_is_valid(ctx))
rc = EVP_VerifyFinal(&ectx, sig, siglen, ctx->exkey);
}
if (!rc) {
ERR_load_crypto_strings();
info("Credential signature check: %s", _ssl_error());
rc = SLURM_ERROR;
} else
rc = SLURM_SUCCESS;
#ifdef HAVE_EVP_MD_CTX_CLEANUP
/* Note: Likely memory leak if this function is absent */
EVP_MD_CTX_cleanup(&ectx);
#endif
free_buf(buffer);
return rc;
}
static void
_pack_cred(slurm_cred_t cred, Buf buffer)
{
pack32( cred->jobid, buffer);
pack32( cred->stepid, buffer);
pack32((uint32_t) cred->uid, buffer);
packstr( cred->nodes, buffer);
pack32( cred->alloc_lps_cnt, buffer);
if (cred->alloc_lps_cnt > 0)
pack32_array( cred->alloc_lps, cred->alloc_lps_cnt, buffer);
pack_time( cred->ctime, buffer);
}
static bool
_credential_replayed(slurm_cred_ctx_t ctx, slurm_cred_t cred)
{
ListIterator i = NULL;
cred_state_t *s = NULL;
_clear_expired_credential_states(ctx);
i = list_iterator_create(ctx->state_list);
while ((s = list_next(i))) {
if ((s->jobid == cred->jobid) && (s->stepid == cred->stepid))
break;
}
list_iterator_destroy(i);
/*
* If we found a match, this credential is being replayed.
*/
if (s) return true;
/*
* Otherwise, save the credential state
*/
_insert_cred_state(ctx, cred);
return false;
}
#ifdef DISABLE_LOCALTIME
extern char * timestr (const time_t *tp, char *buf, size_t n)
#else
static char * timestr (const time_t *tp, char *buf, size_t n)
#endif
{
char fmt[] = "%y%m%d%H%M%S";
struct tm tmval;
#ifdef DISABLE_LOCALTIME
static int disabled = 0;
if (buf == NULL) disabled=1;
if (disabled) return NULL;
#endif
if (!localtime_r (tp, &tmval))
error ("localtime_r: %m");
strftime (buf, n, fmt, &tmval);
return (buf);
}
extern void
slurm_cred_handle_reissue(slurm_cred_ctx_t ctx, slurm_cred_t cred)
{
job_state_t *j = _find_job_state(ctx, cred->jobid);
if (j != NULL && j->revoked && cred->ctime > j->revoked) {
/* The credential has been reissued. Purge the
old record so that "cred" will look like a new
credential to any ensuing commands. */
info("reissued job credential for job %u", j->jobid);
/* Setting j->expiration to zero will make
_clear_expired_job_states() remove this job credential
from the cred context. */
j->expiration = 0;
_clear_expired_job_states(ctx);
}
}
extern bool
slurm_cred_revoked(slurm_cred_ctx_t ctx, slurm_cred_t cred)
{
job_state_t *j = _find_job_state(ctx, cred->jobid);
if ((j == NULL) || (j->revoked == (time_t)0))
return false;
if (cred->ctime <= j->revoked)
return true;
return false;
}
static bool
_credential_revoked(slurm_cred_ctx_t ctx, slurm_cred_t cred)
{
job_state_t *j = NULL;
_clear_expired_job_states(ctx);
if (!(j = _find_job_state(ctx, cred->jobid))) {
(void) _insert_job_state(ctx, cred->jobid);
return false;
}
if (cred->ctime <= j->revoked) {
char buf[64];
debug ("cred for %d revoked. expires at %s",
j->jobid, timestr (&j->expiration, buf, 64));
return true;
}
return false;
}
static job_state_t *
_find_job_state(slurm_cred_ctx_t ctx, uint32_t jobid)
{
ListIterator i = NULL;
job_state_t *j = NULL;
i = list_iterator_create(ctx->job_list);
while ((j = list_next(i)) && (j->jobid != jobid)) {;}
list_iterator_destroy(i);
return j;
}
static int
_find_cred_state(cred_state_t *c, slurm_cred_t cred)
{
return ((c->jobid == cred->jobid) && (c->stepid == cred->stepid));
}
static job_state_t *
_insert_job_state(slurm_cred_ctx_t ctx, uint32_t jobid)
{
job_state_t *j = _job_state_create(jobid);
list_append(ctx->job_list, j);
return j;
}
static job_state_t *
_job_state_create(uint32_t jobid)
{
job_state_t *j = xmalloc(sizeof(*j));
j->jobid = jobid;
j->revoked = (time_t) 0;
j->ctime = time(NULL);
j->expiration = (time_t) MAX_TIME;
return j;
}
static void
_job_state_destroy(job_state_t *j)
{
debug3 ("destroying job %u state", j->jobid);
xfree(j);
}
static void
_clear_expired_job_states(slurm_cred_ctx_t ctx)
{
char t1[64], t2[64], t3[64];
time_t now = time(NULL);
ListIterator i = NULL;
job_state_t *j = NULL;
i = list_iterator_create(ctx->job_list);
while ((j = list_next(i))) {
if (j->revoked) {
strcpy(t2, " revoked:");
timestr(&j->revoked, (t2+9), (64-9));
} else {
t2[0] = '\0';
}
if (j->expiration) {
strcpy(t3, " expires:");
timestr(&j->revoked, (t3+9), (64-9));
} else {
t3[0] = '\0';
}
debug3("job state %u: ctime:%s%s%s",
j->jobid, timestr(&j->ctime, t1, 64), t2, t3);
if (j->revoked && (now > j->expiration)) {
list_delete(i);
}
}
list_iterator_destroy(i);
}
static void
_clear_expired_credential_states(slurm_cred_ctx_t ctx)
{
time_t now = time(NULL);
ListIterator i = NULL;
cred_state_t *s = NULL;
i = list_iterator_create(ctx->state_list);
while ((s = list_next(i))) {
if (now > s->expiration)
list_delete(i);
}
list_iterator_destroy(i);
}
static void
_insert_cred_state(slurm_cred_ctx_t ctx, slurm_cred_t cred)
{
cred_state_t *s = _cred_state_create(ctx, cred);
list_append(ctx->state_list, s);
}
static cred_state_t *
_cred_state_create(slurm_cred_ctx_t ctx, slurm_cred_t cred)
{
cred_state_t *s = xmalloc(sizeof(*s));
s->jobid = cred->jobid;
s->stepid = cred->stepid;
s->expiration = cred->ctime + ctx->expiry_window;
return s;
}
static void
_cred_state_destroy(cred_state_t *s)
{
xfree(s);
}
static void
_cred_state_pack_one(cred_state_t *s, Buf buffer)
{
pack32(s->jobid, buffer);
pack32(s->stepid, buffer);
pack_time(s->expiration, buffer);
}
static cred_state_t *
_cred_state_unpack_one(Buf buffer)
{
cred_state_t *s = xmalloc(sizeof(*s));
safe_unpack32(&s->jobid, buffer);
safe_unpack32(&s->stepid, buffer);
safe_unpack_time(&s->expiration, buffer);
return s;
unpack_error:
_cred_state_destroy(s);
return NULL;
}
static void
_job_state_pack_one(job_state_t *j, Buf buffer)
{
pack32(j->jobid, buffer);
pack_time(j->revoked, buffer);
pack_time(j->ctime, buffer);
pack_time(j->expiration, buffer);
}
static job_state_t *
_job_state_unpack_one(Buf buffer)
{
char t1[64], t2[64], t3[64];
job_state_t *j = xmalloc(sizeof(*j));
safe_unpack32( &j->jobid, buffer);
safe_unpack_time( &j->revoked, buffer);
safe_unpack_time( &j->ctime, buffer);
safe_unpack_time( &j->expiration, buffer);
if (j->revoked) {
strcpy(t2, " revoked:");
timestr(&j->revoked, (t2+9), (64-9));
} else {
t2[0] = '\0';
}
if (j->expiration) {
strcpy(t3, " expires:");
timestr(&j->revoked, (t3+9), (64-9));
} else {
t3[0] = '\0';
}
debug3("cred_unpack: job %u ctime:%s%s%s",
j->jobid, timestr (&j->ctime, t1, 64), t2, t3);
if ((j->revoked) && (j->expiration == (time_t) MAX_TIME)) {
info ("Warning: revoke on job %u has no expiration",
j->jobid);
j->expiration = j->revoked + 600;
}
return j;
unpack_error:
_job_state_destroy(j);
return NULL;
}
static void
_cred_state_pack(slurm_cred_ctx_t ctx, Buf buffer)
{
ListIterator i = NULL;
cred_state_t *s = NULL;
pack32(list_count(ctx->state_list), buffer);
i = list_iterator_create(ctx->state_list);
while ((s = list_next(i)))
_cred_state_pack_one(s, buffer);
list_iterator_destroy(i);
}
static void
_cred_state_unpack(slurm_cred_ctx_t ctx, Buf buffer)
{
time_t now = time(NULL);
uint32_t n;
int i = 0;
cred_state_t *s = NULL;
safe_unpack32(&n, buffer);
for (i = 0; i < n; i++) {
if (!(s = _cred_state_unpack_one(buffer)))
goto unpack_error;
if (now < s->expiration)
list_append(ctx->state_list, s);
}
return;
unpack_error:
error("Unable to unpack job credential state information");
return;
}
static void
_job_state_pack(slurm_cred_ctx_t ctx, Buf buffer)
{
ListIterator i = NULL;
job_state_t *j = NULL;
pack32((uint32_t) list_count(ctx->job_list), buffer);
i = list_iterator_create(ctx->job_list);
while ((j = list_next(i)))
_job_state_pack_one(j, buffer);
list_iterator_destroy(i);
}
static void
_job_state_unpack(slurm_cred_ctx_t ctx, Buf buffer)
{
time_t now = time(NULL);
uint32_t n = 0;
int i = 0;
job_state_t *j = NULL;
safe_unpack32(&n, buffer);
for (i = 0; i < n; i++) {
if (!(j = _job_state_unpack_one(buffer)))
goto unpack_error;
if (!j->revoked || (j->revoked && (now < j->expiration)))
list_append(ctx->job_list, j);
else
debug3 ("not appending expired job %u state", j->jobid);
}
return;
unpack_error:
error("Unable to unpack job state information");
return;
}