first commit

This commit is contained in:
2024-10-28 23:04:48 +01:00
commit 1ee55157f1
911 changed files with 325331 additions and 0 deletions

2
authservice/mqsimpleauth/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/testSecret*
/*.log

View File

@@ -0,0 +1,59 @@
# © Copyright IBM Corporation 2017, 2022
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This Makefile expects the following to be installed:
# - gcc
# - ldd
# - MQ SDK (mqm_r library, plus header files)
SRC_DIR = src
BUILD_DIR = ./build
ARCH ?= $(if $(findstring x86_64,$(shell uname -m)),amd64,$(if $(findstring aarch64,$(shell uname -m)),aarch64,$(shell uname -m)))
# Flags passed to the C compiler. Need to use gnu11 to get POSIX functions needed for file locking.
CFLAGS.amd64 := -m64
CFLAGS.ppc64le := -m64
CFLAGS.s390x := -m64
# -m64 is not a valid compiler option on aarch64/arm64 (ARM)
CFLAGS.arm64 :=
CFLAGS += -std=gnu11 -fPIC -Wall ${CFLAGS.${ARCH}}
LIB_MQ = -L/opt/mqm/lib64 -lmqm_r
all: $(BUILD_DIR)/mqsimpleauth.so $(BUILD_DIR)/simpleauth_test $(BUILD_DIR)/log_test
$(BUILD_DIR)/log.o : $(SRC_DIR)/log.c $(SRC_DIR)/log.h
mkdir -p ${dir $@}
gcc $(CFLAGS) -c $(SRC_DIR)/log.c -o $@
$(BUILD_DIR)/log_test : $(BUILD_DIR)/log.o
mkdir -p ${dir $@}
gcc $(CFLAGS) $(SRC_DIR)/log_test.c $^ -o $@
# Run Logging tests, and print log if they fail
$@ || (cat log_test*.log && exit 1)
$(BUILD_DIR)/simpleauth.o : $(SRC_DIR)/simpleauth.c $(SRC_DIR)/simpleauth.h
mkdir -p ${dir $@}
gcc $(CFLAGS) -c $(SRC_DIR)/simpleauth.c -o $@
$(BUILD_DIR)/simpleauth_test : $(BUILD_DIR)/simpleauth.o $(BUILD_DIR)/log.o
mkdir -p ${dir $@}
gcc $(CFLAGS) -lpthread $(SRC_DIR)/simpleauth_test.c $^ -o $@
# Run SimpleAuth tests, and print log if they fail
$@ || (cat simpleauth_test*.log && exit 1)
$(BUILD_DIR)/mqsimpleauth.so : $(BUILD_DIR)/log.o $(BUILD_DIR)/simpleauth.o
mkdir -p ${dir $@}
gcc $(CFLAGS) -I/opt/mqm/inc -D_REENTRANT $(LIB_MQ) -Wl,-rpath,/opt/mqm/lib64 -Wl,-rpath,/usr/lib64 -shared $(SRC_DIR)/mqsimpleauth.c $^ -o $@
ldd $@

View File

@@ -0,0 +1,162 @@
/*
© Copyright IBM Corporation 2021, 2022
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
FILE *fp = NULL;
int pid;
char hostname[255];
bool debug = false;
/**
* Determine whether debugging is enabled or not, using an environment variable.
*/
void init_debug(){
char *debug_env = getenv("DEBUG");
if (debug_env != NULL)
{
// Enable debug logging if the DEBUG environment variable is set
if (strncmp(debug_env, "true", 4) || strncmp(debug_env, "1", 1))
{
debug = true;
}
}
}
/**
* Internal function to initialize the log with the given file mode.
*/
int log_init_internal(char *filename, const char *mode)
{
int result = 0;
pid = getpid();
hostname[254] = '\0';
gethostname(hostname, 254);
if (!fp)
{
fp = fopen(filename, "a");
if (fp)
{
// Disable buffering for this file
setbuf(fp, NULL);
}
else
{
result = 1;
}
}
init_debug();
return result;
}
int log_init_reset(char *filename)
{
// Open the log file for writing (overwrite if it already exists)
return log_init_internal(filename, "w");
}
int log_init(char *filename)
{
// Open the log file file for appending
return log_init_internal(filename, "a");
}
void log_init_file(FILE *f)
{
fp = f;
init_debug();
}
void log_close()
{
if (fp)
{
fclose(fp);
fp = NULL;
}
}
void log_printf(const char *source_file, int source_line, const char *level, const char *format, ...)
{
if (fp)
{
// If this is a DEBUG message, and debugging is off
if ((strncmp(level, "DEBUG", 5) == 0) && !debug)
{
return;
}
char buf[1024] = "";
char *cur = buf;
char* const end = buf + sizeof buf;
char date_buf[70];
struct tm *utc;
time_t t;
struct timeval now;
gettimeofday(&now, NULL);
t = now.tv_sec;
t = time(NULL);
utc = gmtime(&t);
cur += snprintf(cur, end-cur, "{");
cur += snprintf(cur, end-cur, "\"loglevel\":\"%s\"", level);
// Print ISO-8601 time and date
if (strftime(date_buf, sizeof date_buf, "%FT%T", utc))
{
// Round microseconds down to milliseconds, for consistency
cur += snprintf(cur, end-cur, ", \"ibm_datetime\":\"%s.%03ldZ\"", date_buf, now.tv_usec / (long)1000);
}
cur += snprintf(cur, end-cur, ", \"ibm_processId\":\"%d\"", pid);
cur += snprintf(cur, end-cur, ", \"host\":\"%s\"", hostname);
cur += snprintf(cur, end-cur, ", \"module\":\"%s:%d\"", source_file, source_line);
cur += snprintf(cur, end-cur, ", \"message\":\"");
if (strncmp(level, "DEBUG", 5) == 0)
{
// Add a prefix on any debug messages
cur += snprintf(cur, end-cur, "mqsimpleauth: ");
}
// Print log message, using varargs
va_list args;
va_start(args, format);
cur += vsnprintf(cur, end-cur, format, args);
va_end(args);
cur += snprintf(cur, end-cur, "\"}\n");
// Important: Just do one file write, to prevent problems with multi-threading.
// This only works if the log message is not too long for the buffer.
fprintf(fp, "%s", buf);
}
}
int trimmed_len(char *s, int max_len)
{
int i;
for (i = max_len - 1; i >= 0; i--)
{
if (s[i] != ' ')
break;
}
return i+1;
}

View File

@@ -0,0 +1,70 @@
/*
© Copyright IBM Corporation 2021, 2022
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef _LOG_H
#define _LOG_H
/**
* Initialize the log to use the given file name, wiping any existing contents.
*/
int log_init_reset(char *filename);
/**
* Initialize the log to use the given file name.
*/
int log_init(char *filename);
/**
* Initialize the log with an existing file handle.
*/
void log_init_file(FILE *f);
/**
* Write a message to the log file, based on a printf format string.
*
* @param source_file the name of the source code file submitting this log message
* @param source_line the line of code in the source file
* @param level the log level, one of "DEBUG", "INFO" or "ERROR"
* @param format the printf format string for the message
*/
void log_printf(const char *source_file, int source_line, const char *level, const char *format, ...);
void log_close();
/**
* Variadic macro to write an informational message to the log file, based on a printf format string.
*/
#define log_infof(format,...) log_printf(__FILE__, __LINE__, "INFO", format, ##__VA_ARGS__)
/**
* Variadic macro to write an error message to the log file, based on a printf format string.
*/
#define log_errorf(format,...) log_printf(__FILE__, __LINE__, "ERROR", format, ##__VA_ARGS__)
/**
* Variadic macro to write a debug message to the log file, based on a printf format string.
*/
#define log_debugf(format,...) log_printf(__FILE__, __LINE__, "DEBUG", format, ##__VA_ARGS__)
/**
* Return the length of the string when trimmed of trailing spaces.
* IBM MQ uses fixed length strings, so this function can be used to print
* a trimmed version of a string using the "%.*s" printf format string.
* For example, `log_printf("%.*s", trimmed_len(fw_str, 48), fw_str)`
*/
int trimmed_len(char *s, int);
#endif

View File

@@ -0,0 +1,120 @@
/*
© Copyright IBM Corporation 2022
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "log.h"
// Headers for multi-threaded tests
#include <pthread.h>
// Start a test and log the function name
#define test_start() printf("=== RUN: %s\n", __func__)
// Indicate test has passed
#define test_pass() printf("--- PASS: %s\n", __func__)
// The length of strings used in the tests
#define STR_LEN 5
// Indicate test has failed
void test_fail(const char *test_name)
{
printf("--- FAIL: %s\n", test_name);
exit(1);
}
// Print a fixed-width string in hexadecimal
void print_hex(char fw_string[STR_LEN])
{
printf("[");
for (int i=0; i<STR_LEN; i++)
{
printf("%02x", fw_string[i]);
if (i < STR_LEN-1)
printf(",");
}
printf("]");
}
// ----------------------------------------------------------------------------
// Tests for string manipulation
// ----------------------------------------------------------------------------
void test_trimmed_len(const char *test_name, char fw_string[STR_LEN], int expected_len)
{
printf("=== RUN: %s\n", test_name);
int len;
// Create a copy of the fixed-width string
char fw_string2[STR_LEN];
memcpy(fw_string2, fw_string, STR_LEN * sizeof(char));
// Call the function under test
len = trimmed_len(fw_string, STR_LEN);
// Check the result is correct
if (len != expected_len)
{
printf("%s: Expected result to be %d; got %d\n", __func__, expected_len, len);
test_fail(test_name);
}
// Check that the original string has not been changed
for (int i=0; i<STR_LEN; i++)
{
if (fw_string[i] != fw_string2[i])
{
printf("%c-%c\n", fw_string[i], fw_string2[i]);
printf("%s: Expected string to be identical to input hex ", __func__);
print_hex(fw_string2);
printf("; got hex ");
print_hex(fw_string);
printf("\n");
test_fail(test_name);
}
}
printf("--- PASS: %s\n", test_name);
}
void test_trimmed_len_normal()
{
char fw_string[STR_LEN] = {'a','b','c',' ',' '};
test_trimmed_len(__func__, fw_string, 3);
}
void test_trimmed_len_full()
{
char fw_string[STR_LEN] = {'a','b','c','d','e'};
test_trimmed_len(__func__, fw_string, 5);
}
void test_trimmed_len_empty()
{
char fw_string[STR_LEN] = {' ',' ',' ',' ',' '};
test_trimmed_len(__func__, fw_string, 0);
}
// ----------------------------------------------------------------------------
int main()
{
// Turn on debugging for the tests
setenv("DEBUG", "true", true);
log_init("log_test.log");
test_trimmed_len_normal();
test_trimmed_len_full();
test_trimmed_len_empty();
log_close();
}

View File

@@ -0,0 +1 @@
fred:$2y$05$3Fp9

View File

@@ -0,0 +1,336 @@
/*
© Copyright IBM Corporation 2021, 2024
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// This is a developer only configuration and not recommended for production usage.
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cmqec.h>
#include "log.h"
#include "simpleauth.h"
// Declare the internal functions that implement the interface
MQZ_INIT_AUTHORITY MQStart;
static MQZ_AUTHENTICATE_USER mqsimpleauth_authenticate_user;
static MQZ_FREE_USER mqsimpleauth_free_user;
static MQZ_TERM_AUTHORITY mqsimpleauth_terminate;
#define LOG_FILE "/var/mqm/errors/simpleauth.json"
#define NAME "MQ Advanced for Developers custom authentication service"
/**
* Initialization and entrypoint for the dynamically loaded
* authorization installable service. It registers the addresses of the
* other functions which are to be called by the queue manager.
*
* This function is called whenever the module is loaded. The Options
* field will show whether it's a PRIMARY (i.e. during qmgr startup) or
* SECONDARY.
*/
void MQENTRY MQStart(
MQHCONFIG hc,
MQLONG Options,
MQCHAR48 QMgrName,
MQLONG ComponentDataLength,
PMQBYTE ComponentData,
PMQLONG Version,
PMQLONG pCompCode,
PMQLONG pReason)
{
MQLONG CC = MQCC_OK;
MQLONG Reason = MQRC_NONE;
int log_rc = 0;
if (Options == MQZIO_PRIMARY)
{
// Reset the log file. The file could still get large if debug is turned on,
// but this is a simpler solution for now.
log_rc = log_init_reset(LOG_FILE);
}
else
{
log_rc = log_init(LOG_FILE);
}
if (log_rc != 0)
{
CC = MQCC_FAILED;
Reason = MQRC_INITIALIZATION_FAILED;
}
if (Options == MQZIO_PRIMARY)
{
log_infof("Initializing %s", NAME);
}
log_debugf("MQStart options=%s qmgr=%.*s", ((Options == MQZIO_SECONDARY) ? "Secondary" : "Primary"), trimmed_len(QMgrName, MQ_Q_MGR_NAME_LENGTH), QMgrName);
// Initialize the functions to use for each entry point
if (CC == MQCC_OK)
{
hc->MQZEP_Call(hc, MQZID_INIT_AUTHORITY, (PMQFUNC)MQStart, &CC, &Reason);
}
if (CC == MQCC_OK)
{
hc->MQZEP_Call(hc, MQZID_TERM_AUTHORITY, (PMQFUNC)mqsimpleauth_terminate, &CC, &Reason);
}
if (CC == MQCC_OK)
{
hc->MQZEP_Call(hc, MQZID_AUTHENTICATE_USER, (PMQFUNC)mqsimpleauth_authenticate_user, &CC, &Reason);
}
if (CC == MQCC_OK)
{
hc->MQZEP_Call(hc, MQZID_FREE_USER, (PMQFUNC)mqsimpleauth_free_user, &CC, &Reason);
}
*Version = MQZAS_VERSION_6;
*pCompCode = CC;
*pReason = Reason;
return;
}
/**
* Called during the connection of any application which supplies an MQCSP (Connection Security Parameters).
* This is the usual case.
* See https://www.ibm.com/support/knowledgecenter/SSFKSJ_latest/com.ibm.mq.ref.dev.doc/q095610_.html
*/
static void MQENTRY mqsimpleauth_authenticate_user_csp(
PMQCHAR pQMgrName,
PMQCSP pSecurityParms,
PMQZAC pApplicationContext,
PMQZIC pIdentityContext,
PMQPTR pCorrelationPtr,
PMQBYTE pComponentData,
PMQLONG pContinuation,
PMQLONG pCompCode,
PMQLONG pReason)
{
char *csp_user = NULL;
char *csp_pass = NULL;
// Firstly, create null-terminated strings from the user credentials in the MQ CSP object
csp_user = malloc(pSecurityParms->CSPUserIdLength + 1);
if (!csp_user)
{
log_errorf("%s is unable to allocate memory for a user", NAME);
*pCompCode = MQCC_FAILED;
*pReason = MQRC_SERVICE_ERROR;
return;
}
strncpy(csp_user, pSecurityParms->CSPUserIdPtr, pSecurityParms->CSPUserIdLength);
csp_user[pSecurityParms->CSPUserIdLength] = 0;
csp_pass = malloc((pSecurityParms->CSPPasswordLength + 1));
if (!csp_pass)
{
log_errorf("%s is unable to allocate memory for a password", NAME);
*pCompCode = MQCC_FAILED;
*pReason = MQRC_SERVICE_ERROR;
if (csp_user)
{
free(csp_user);
}
return;
}
strncpy(csp_pass, pSecurityParms->CSPPasswordPtr, pSecurityParms->CSPPasswordLength);
csp_pass[pSecurityParms->CSPPasswordLength] = 0;
log_debugf("%s with CSP user set. user=%s", __func__, csp_user);
int auth_result = simpleauth_authenticate_user(csp_user, csp_pass);
if (auth_result == SIMPLEAUTH_VALID)
{
// An OK completion code means MQ will accept this user is authenticated
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
// Tell the queue manager to stop trying other authorization services.
*pContinuation = MQZCI_STOP;
memcpy(pIdentityContext->UserIdentifier, csp_user, sizeof(pIdentityContext->UserIdentifier));
log_debugf("Authenticated user=%s", pIdentityContext->UserIdentifier);
}
// If the simpleauth file does not have an entry for this user
else if (auth_result == SIMPLEAUTH_INVALID_USER)
{
*pCompCode = MQCC_WARNING;
*pReason = MQRC_NONE;
// Tell the queue manager to continue trying other authorization services, as they might have the user.
*pContinuation = MQZCI_CONTINUE;
log_debugf(
"User authentication failed due to invalid user. user=%.*s effuser=%.*s applname=%.*s csp_user=%s cc=%d reason=%d",
trimmed_len(pIdentityContext->UserIdentifier, MQ_USER_ID_LENGTH),
pIdentityContext->UserIdentifier,
trimmed_len(pApplicationContext->EffectiveUserID, MQ_USER_ID_LENGTH),
pApplicationContext->EffectiveUserID,
trimmed_len(pApplicationContext->ApplName, MQ_APPL_NAME_LENGTH),
pApplicationContext->ApplName,
csp_user,
*pCompCode,
*pReason);
}
// If the simpleauth file has an entry for this user, but the password supplied is incorrect
else if (auth_result == SIMPLEAUTH_INVALID_PASSWORD)
{
*pCompCode = MQCC_WARNING;
*pReason = MQRC_NOT_AUTHORIZED;
// Tell the queue manager to stop trying other authorization services.
*pContinuation = MQZCI_STOP;
log_debugf(
"User authentication failed due to invalid password. user=%.*s effuser=%.*s applname=%.*s csp_user=%s cc=%d reason=%d",
trimmed_len(pIdentityContext->UserIdentifier, MQ_USER_ID_LENGTH),
pIdentityContext->UserIdentifier,
trimmed_len(pApplicationContext->EffectiveUserID, MQ_USER_ID_LENGTH),
pApplicationContext->EffectiveUserID,
trimmed_len(pApplicationContext->ApplName, MQ_APPL_NAME_LENGTH),
pApplicationContext->ApplName,
csp_user,
*pCompCode,
*pReason);
}
if (csp_user)
{
free(csp_user);
}
if (csp_pass)
{
free(csp_pass);
}
return;
}
/**
* Called during the connection of any application.
* For more information on the parameters, see https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_latest/com.ibm.mq.ref.dev.doc/q110090_.html
*/
static void MQENTRY mqsimpleauth_authenticate_user(
PMQCHAR pQMgrName,
PMQCSP pSecurityParms,
PMQZAC pApplicationContext,
PMQZIC pIdentityContext,
PMQPTR pCorrelationPtr,
PMQBYTE pComponentData,
PMQLONG pContinuation,
PMQLONG pCompCode,
PMQLONG pReason)
{
char *spuser = NULL;
// By default, return a warning, which indicates to MQ that this
// authorization service hasn't authenticated the user.
*pCompCode = MQCC_WARNING;
*pReason = MQRC_NONE;
// By default, tell the queue manager to continue trying other
// authorization services.
*pContinuation = MQZCI_CONTINUE;
if ((pSecurityParms->AuthenticationType) == MQCSP_AUTH_USER_ID_AND_PWD)
{
mqsimpleauth_authenticate_user_csp(pQMgrName, pSecurityParms, pApplicationContext, pIdentityContext, pCorrelationPtr, pComponentData, pContinuation, pCompCode, pReason);
}
else
{
// Password not supplied, so just check that the user ID is valid
spuser = malloc(sizeof(PMQCHAR12) + 1);
if (!spuser)
{
log_errorf("%s is unable to allocate memory to check a user", NAME);
*pCompCode = MQCC_FAILED;
*pReason = MQRC_SERVICE_ERROR;
return;
}
strncpy(spuser, pApplicationContext->EffectiveUserID, strlen(pApplicationContext->EffectiveUserID));
spuser[sizeof(PMQCHAR12)] = 0;
log_debugf("%s without CSP user set. effectiveuid=%s env=%d, callertype=%d, type=%d, accttoken=%d applidentitydata=%d", __func__, spuser, pApplicationContext->Environment, pApplicationContext->CallerType, pApplicationContext->AuthenticationType, pIdentityContext->AccountingToken, pIdentityContext->ApplIdentityData);
if (strncmp(spuser, "mqm", 3) == 0)
{
// Special case: pass the "mqm" user on for validation up the chain
// A warning in the completion code means MQ will pass this to other authorization services
*pCompCode = MQCC_WARNING;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
}
else
{
bool valid_user = simpleauth_valid_user(spuser);
if (valid_user)
{
// An OK completion code means MQ will accept this user is authenticated
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_STOP;
memcpy(pIdentityContext->UserIdentifier, spuser, sizeof(pIdentityContext->UserIdentifier));
}
else
{
log_debugf(
"User authentication failed user=%.*s effuser=%.*s applname=%.*s cspuser=%s cc=%d reason=%d",
trimmed_len(pIdentityContext->UserIdentifier, MQ_USER_ID_LENGTH),
pIdentityContext->UserIdentifier,
trimmed_len(pApplicationContext->EffectiveUserID, MQ_USER_ID_LENGTH),
pApplicationContext->EffectiveUserID,
trimmed_len(pApplicationContext->ApplName, MQ_APPL_NAME_LENGTH),
pApplicationContext->ApplName,
spuser,
*pCompCode,
*pReason);
}
if (spuser)
{
free(spuser);
}
}
}
return;
}
/**
* Called during MQDISC, as the inverse of the call to authenticate.
*/
static void MQENTRY mqsimpleauth_free_user(
PMQCHAR pQMgrName,
PMQZFP pFreeParms,
PMQBYTE pComponentData,
PMQLONG pContinuation,
PMQLONG pCompCode,
PMQLONG pReason)
{
log_debugf("mqsimpleauth_freeuser()");
*pCompCode = MQCC_WARNING;
*pReason = MQRC_NONE;
*pContinuation = MQZCI_CONTINUE;
}
/**
* Called when the authorization service is terminated.
*/
static void MQENTRY mqsimpleauth_terminate(
MQHCONFIG hc,
MQLONG Options,
PMQCHAR pQMgrName,
PMQBYTE pComponentData,
PMQLONG pCompCode,
PMQLONG pReason)
{
if (Options == MQZTO_PRIMARY)
{
log_infof("Terminating %s", NAME);
log_close();
}
else {
log_debugf("Terminating secondary");
}
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
}

View File

@@ -0,0 +1,153 @@
/*
© Copyright IBM Corporation 2021, 2024
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "log.h"
#include "simpleauth.h"
#include <linux/limits.h>
const char *_mq_app_secret_file = MQ_APP_SECRET_FILE_DEFAULT;
const char *_mq_admin_secret_file = MQ_ADMIN_SECRET_FILE_DEFAULT;
// Check if the user is valid
int simpleauth_authenticate_user(const char *const user, const char *const password)
{
int result = -1;
if (simpleauth_valid_user(user))
{
char *pwd = get_secret_for_user(user);
if (pwd != NULL)
{
if (strcmp(pwd, password) == 0)
{
log_debugf("Correct password supplied. user=%s", user);
result = SIMPLEAUTH_VALID;
}
else
{
log_debugf("Incorrect password supplied. user=%s", user);
result = SIMPLEAUTH_INVALID_PASSWORD;
}
memset(pwd, 0, strlen(pwd));
free(pwd);
}
else
{
log_debugf("Failed to get secret for user '%s'", user);
result = SIMPLEAUTH_INVALID_PASSWORD;
}
}
else
{
log_debugf("User does not exist. user=%s", user);
result = SIMPLEAUTH_INVALID_USER;
}
return result;
}
bool simpleauth_valid_user(const char *const user)
{
bool valid = false;
if ((strcmp(user, APP_USER_NAME) == 0 || strcmp(user, ADMIN_USER_NAME) == 0))
{
valid = true;
}
return valid;
}
/**
* get_secret_for_user will return a char* containing the credential for the given user
* the credential is read from the filesystem if the relevant file exists and an environment
* variable if not
*
* The caller is responsible for clearing then freeing memory
*/
char *get_secret_for_user(const char *const user)
{
if (0 == strcmp(user, APP_USER_NAME))
{
char *secret = read_secret(_mq_app_secret_file);
if (secret != NULL)
{
return secret;
}
else
{
const char *pwdFromEnv = getenv("MQ_APP_PASSWORD");
if (pwdFromEnv != NULL)
{
log_infof("Environment variable MQ_APP_PASSWORD is deprecated, use secrets to set the passwords");
}
return strdup(pwdFromEnv);
}
}
else if (0 == strcmp(user, ADMIN_USER_NAME))
{
char *secret = read_secret(_mq_admin_secret_file);
if (secret != NULL)
{
return secret;
}
else
{
const char *pwdFromEnv = getenv("MQ_ADMIN_PASSWORD");
if (pwdFromEnv != NULL)
{
log_infof("Environment variable MQ_ADMIN_PASSWORD is deprecated, use secrets to set the passwords");
}
return strdup(pwdFromEnv);
}
}
else
{
return NULL;
}
}
/**
* read_secret will return a char* containing the credential read from the filesystem for the given user
*
* The caller is responsible for clearing then freeing memory
*/
char *read_secret(const char *const secret)
{
FILE *fp = fopen(secret, "r");
if (fp)
{
const int line_size = MAX_PASSWORD_LENGTH + 1;
char *pwd = malloc(line_size);
char *result;
result = fgets(pwd, line_size, fp);
fclose(fp);
if (result == NULL)
{
memset(pwd, 0, line_size);
free(pwd);
return NULL;
}
result[strcspn(result, "\r\n")] = 0;
return result;
}
else
{
return NULL;
}
}

View File

@@ -0,0 +1,62 @@
/*
© Copyright IBM Corporation 2021, 2024
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef _SIMPLEAUTH_H
#define _SIMPLEAUTH_H
#define SIMPLEAUTH_VALID 0
#define SIMPLEAUTH_INVALID_USER 1
#define SIMPLEAUTH_INVALID_PASSWORD 2
#define MQ_APP_SECRET_FILE_DEFAULT "/run/secrets/mqAppPassword"
#define MQ_ADMIN_SECRET_FILE_DEFAULT "/run/secrets/mqAdminPassword"
#define APP_USER_NAME "app"
#define ADMIN_USER_NAME "admin"
#define MAX_PASSWORD_LENGTH 256
extern const char *_mq_app_secret_file;
extern const char *_mq_admin_secret_file;
/**
* Authenticate a user, based on the supplied file name.
*
* @param user the user name to authenticate
* @param password the password of the user
* @return SIMPLEAUTH_VALID, SIMPLEAUTH_INVALID_USER or SIMPLEAUTH_INVALID_PASSWORD
*/
int simpleauth_authenticate_user(const char *const user, const char *const password);
/**
* Validate that a user exists in the password file.
*
* @param user the user name to validate
*/
bool simpleauth_valid_user(const char *const user);
/**
* Get the secret of the UserId.
*
* @param user the user name to validate
*/
char *get_secret_for_user(const char *const user);
/**
* Get the secret of the UserId.
*
* @param secret path for the secret file
*/
char *read_secret(const char *const secret);
#endif

View File

@@ -0,0 +1,412 @@
/*
© Copyright IBM Corporation 2021, 2024
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "log.h"
#include "simpleauth.h"
#include "simpleauth_test.h"
#include <stdlib.h>
#include <string.h>
// Headers for multi-threaded tests
#include <pthread.h>
// Start a test and log the function name
#define test_start() printf("=== RUN: %s\n", __func__)
// Indicate test has passed
#define test_pass() printf("--- PASS: %s\n", __func__)
// Indicate test has failed
void test_fail(const char *test_name)
{
printf("--- FAIL: %s\n", test_name);
exit(1);
}
// ----------------------------------------------------------------------------
// Simple test to read secret
// ----------------------------------------------------------------------------
void test_read_secret_ok()
{
test_start();
char *pwd = read_secret("./src/mqAdminPassword");
char *password = "fred:$2y$05$3Fp9";
if (0 != strcmp(pwd, password))
{
printf("%s: pwd: '%s'; password: '%s'\n", __func__, pwd, password);
test_fail(__func__);
}
test_pass();
}
// ----------------------------------------------------------------------------
// Simple tests for authentication
// ----------------------------------------------------------------------------
void test_simpleauth_valid_user_app_valid()
{
test_start();
bool validUser = simpleauth_valid_user(APP_USER_NAME);
printf("%s: app - %d\n", __func__, validUser);
if (!validUser)
test_fail(__func__);
test_pass();
}
void test_simpleauth_valid_user_admin_valid()
{
test_start();
bool validUser = simpleauth_valid_user(ADMIN_USER_NAME);
printf("%s: admin - %d\n", __func__, validUser);
if (!validUser)
test_fail(__func__);
test_pass();
}
void test_simpleauth_valid_user_george_invalid()
{
test_start();
bool validUser = simpleauth_valid_user("george");
printf("%s: george - %d\n", __func__, validUser);
if (validUser)
test_fail(__func__);
test_pass();
}
void test_simpleauth_authenticate_user_fred_unknown()
{
test_start();
test_set_app_password_env("passw0rd-fred-env");
int rc = simpleauth_authenticate_user("fred", "passw0rd-fred-env");
printf("%s: fred - %d\n", __func__, rc);
if (rc != SIMPLEAUTH_INVALID_USER)
test_fail(__func__);
test_pass();
}
void test_simpleauth_authenticate_user_app_ok()
{
test_start();
test_set_app_password_env("passw0rd-app-env");
int rc = simpleauth_authenticate_user("app", "passw0rd-app-env");
printf("%s: app - %d\n", __func__, rc);
if (rc != SIMPLEAUTH_VALID)
test_fail(__func__);
test_pass();
}
void test_simpleauth_authenticate_user_admin_ok()
{
test_start();
test_set_admin_password_env("passw0rd-admin-env");
int rc = simpleauth_authenticate_user("admin", "passw0rd-admin-env");
printf("%s: admin - %d\n", __func__, rc);
if (rc != SIMPLEAUTH_VALID)
test_fail(__func__);
test_pass();
}
void test_simpleauth_authenticate_user_admin_invalidpasswords()
{
test_start();
test_set_admin_password_env("password-admin-env");
const char *bad_passwords[] = {
"",
"passw0rd-admin-env",
"Password-admin-env",
"pass",
"password",
"password-app",
"password-app-env",
"password-admin-env-123"};
size_t bad_pass_len = sizeof(bad_passwords) / sizeof(bad_passwords[0]);
for (int i = 0; i < bad_pass_len; i++)
{
int rc = simpleauth_authenticate_user("admin", bad_passwords[i]);
printf("%s: admin/%s - %d\n", __func__, bad_passwords[i], rc);
if (rc != SIMPLEAUTH_INVALID_PASSWORD)
test_fail(__func__);
test_pass();
}
}
void test_simpleauth_authenticate_user_admin_secret_file_valid()
{
test_start();
test_set_admin_password_file("password-admin-file");
int rc = simpleauth_authenticate_user("admin", "password-admin-file");
printf("%s: admin - %d\n", __func__, rc);
if (rc != SIMPLEAUTH_VALID)
test_fail(__func__);
test_pass();
}
void test_simpleauth_authenticate_user_admin_secret_file_long()
{
test_start();
const int test_password_length = MAX_PASSWORD_LENGTH + 7;
char test_password[test_password_length];
char truncated_password[MAX_PASSWORD_LENGTH + 1];
for (int i = 0; i < test_password_length; i++)
{
test_password[i] = '0' + ((i + 1) % 10);
if (i < MAX_PASSWORD_LENGTH)
{
truncated_password[i] = test_password[i];
}
}
test_password[test_password_length] = 0;
truncated_password[MAX_PASSWORD_LENGTH] = 0;
test_set_admin_password_file(test_password);
int rc = simpleauth_authenticate_user("admin", test_password);
if (rc != SIMPLEAUTH_INVALID_PASSWORD)
{
printf("%s: admin/'%s' - %d\n", __func__, test_password, rc);
test_fail(__func__);
}
rc = simpleauth_authenticate_user("admin", truncated_password);
if (rc != SIMPLEAUTH_VALID)
{
printf("%s: admin/'%s' - %d\n", __func__, truncated_password, rc);
test_fail(__func__);
}
test_pass();
}
void test_simpleauth_authenticate_user_admin_secret_file_invalid()
{
test_start();
test_set_admin_password_file("password-admin-file");
const char *bad_passwords[] = {
"",
"passw0rd-admin-file",
"Password-admin-file",
"pass",
"password",
"password-app-file",
"password-admin-file-123"};
size_t bad_pass_len = sizeof(bad_passwords) / sizeof(bad_passwords[0]);
for (int i = 0; i < bad_pass_len; i++)
{
int rc = simpleauth_authenticate_user("admin", bad_passwords[i]);
printf("%s: admin/%s - %d\n", __func__, bad_passwords[i], rc);
if (rc != SIMPLEAUTH_INVALID_PASSWORD)
test_fail(__func__);
test_pass();
}
}
void test_simpleauth_authenticate_user_app_secret_file_valid()
{
test_start();
test_set_app_password_file("password-app-file");
int rc = simpleauth_authenticate_user("app", "password-app-file");
printf("%s: admin - %d\n", __func__, rc);
if (rc != SIMPLEAUTH_VALID)
test_fail(__func__);
test_pass();
}
void test_simpleauth_authenticate_user_app_secret_file_invalid()
{
test_start();
test_set_app_password_file("password-app-file");
const char *bad_passwords[] = {
"",
"passw0rd-app-file",
"Password-app-file",
"pass",
"password",
"password-admin-file",
"password-app-file-123"};
size_t bad_pass_len = sizeof(bad_passwords) / sizeof(bad_passwords[0]);
for (int i = 0; i < bad_pass_len; i++)
{
int rc = simpleauth_authenticate_user("app", bad_passwords[i]);
printf("%s: app/%s - %d\n", __func__, bad_passwords[i], rc);
if (rc != SIMPLEAUTH_INVALID_PASSWORD)
test_fail(__func__);
test_pass();
}
}
// ----------------------------------------------------------------------------
// Multi-threaded test
// ----------------------------------------------------------------------------
#define NUM_THREADS 5
// Number of tests to perform per thread. Higher numbers are more likely to trigger timing issue.
#define NUM_TESTS_PER_THREAD 1000
// Maximum number of JSON errors to report (log can get flooded)
#define MAX_JSON_ERRORS 10
// Authenticate multiple users, multiple times
void *authenticate_many_times(void *p)
{
test_set_admin_password_env("passw0rd");
test_set_app_password_env("passw0rd");
for (int i = 0; i < NUM_TESTS_PER_THREAD; i++)
{
int rc = simpleauth_authenticate_user("admin", "passw0rd");
if (rc != SIMPLEAUTH_VALID)
test_fail(__func__);
rc = simpleauth_authenticate_user("app", "passw0rd");
if (rc != SIMPLEAUTH_VALID)
test_fail(__func__);
}
pthread_exit(NULL);
}
void check_log_file_valid(char *filename)
{
int errors = 0;
printf("--- Checking log file is valid\n");
// Check that the JSON log file isn't corrupted
FILE *log = fopen(filename, "r");
if (log == NULL)
{
test_fail(__func__);
}
const size_t line_size = 1024;
char *line = malloc(line_size);
while (fgets(line, line_size, log) != NULL)
{
if ((line[0] != '{') && (errors < MAX_JSON_ERRORS))
{
printf("*** Invalid JSON detected: %s\n", line);
errors++;
}
}
if (line)
{
free(line);
}
fclose(log);
}
// Test authenticate_user with multiple threads, each doing many authentications
void test_simpleauth_authenticate_user_multithreaded(char *logfile)
{
pthread_t threads[NUM_THREADS];
int rc;
test_start();
// Re-initialize the log to use a file for the multi-threaded test
log_init(logfile);
for (int i = 0; i < NUM_THREADS; i++)
{
printf("Creating thread %d\n", i);
rc = pthread_create(&threads[i], NULL, authenticate_many_times, NULL);
if (rc)
{
printf("Error: Unable to create thread, %d\n", rc);
test_fail(__func__);
}
}
// Wait for all the threads to complete
for (int i = 0; i < NUM_THREADS; i++)
{
pthread_join(threads[i], NULL);
}
check_log_file_valid(logfile);
test_pass();
}
// ----------------------------------------------------------------------------
// Test utility functions
// ----------------------------------------------------------------------------
int write_secret(const char *const secretFile, const char *const value)
{
FILE *fp = fopen(secretFile, "w");
if (fp)
{
int rc;
rc = fprintf(fp, "%s\n", value);
fclose(fp);
return rc;
}
else
{
return 1;
}
}
void test_set_admin_password_env(const char *const password)
{
setenv("MQ_ADMIN_PASSWORD", password, 1);
_mq_admin_secret_file = MQ_ADMIN_SECRET_FILE_DEFAULT;
}
void test_set_app_password_env(const char *const password)
{
setenv("MQ_APP_PASSWORD", password, 1);
_mq_app_secret_file = MQ_APP_SECRET_FILE_DEFAULT;
}
void test_set_admin_password_file(const char *const password)
{
write_secret(MQ_ADMIN_SECRET_FILE_TEST, password);
_mq_admin_secret_file = MQ_ADMIN_SECRET_FILE_TEST;
unsetenv("MQ_ADMIN_PASSWORD");
}
void test_set_app_password_file(const char *const password)
{
write_secret(MQ_APP_SECRET_FILE_TEST, password);
_mq_app_secret_file = MQ_APP_SECRET_FILE_TEST;
unsetenv("MQ_APP_PASSWORD");
}
// ----------------------------------------------------------------------------
int main()
{
// Turn on debugging for the tests
setenv("DEBUG", "true", true);
log_init("simpleauth_test.log");
test_read_secret_ok();
test_simpleauth_valid_user_app_valid();
test_simpleauth_valid_user_admin_valid();
test_simpleauth_valid_user_george_invalid();
test_simpleauth_authenticate_user_fred_unknown();
test_simpleauth_authenticate_user_app_ok();
test_simpleauth_authenticate_user_admin_ok();
test_simpleauth_authenticate_user_admin_invalidpasswords();
test_simpleauth_authenticate_user_admin_secret_file_valid();
test_simpleauth_authenticate_user_admin_secret_file_long();
test_simpleauth_authenticate_user_admin_secret_file_invalid();
test_simpleauth_authenticate_user_app_secret_file_valid();
test_simpleauth_authenticate_user_app_secret_file_invalid();
log_close();
// Call multi-threaded test last, because it re-initializes the log to use a file
test_simpleauth_authenticate_user_multithreaded("simpleauth_test_multithreaded.log");
}

View File

@@ -0,0 +1,28 @@
/*
© Copyright IBM Corporation 2024
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef _SIMPLEAUTH_TEST_H
#define _SIMPLEAUTH_TEST_H
#define MQ_ADMIN_SECRET_FILE_TEST "testSecretAdmin"
#define MQ_APP_SECRET_FILE_TEST "testSecretApp"
void test_set_admin_password_env(const char *const password);
void test_set_admin_password_file(const char *const password);
void test_set_app_password_env(const char *const password);
void test_set_app_password_file(const char *const password);
#endif