diff --git a/Dockerfile-server b/Dockerfile-server index 531dacb..e49dceb 100644 --- a/Dockerfile-server +++ b/Dockerfile-server @@ -46,7 +46,6 @@ RUN go build -ldflags "-X \"main.ImageCreated=$(date --iso-8601=seconds)\" -X \" RUN go build ./cmd/chkmqready/ RUN go build ./cmd/chkmqhealthy/ RUN go build ./cmd/runmqdevserver/ -RUN go build -buildmode=c-shared -o amqpasdev.so ./internal/qmgrauth/pas.go RUN go test -v ./cmd/runmqdevserver/... RUN go test -v ./cmd/runmqserver/ RUN go test -v ./cmd/chkmqready/ @@ -115,6 +114,28 @@ USER 1001 ENV MQ_CONNAUTH_USE_HTP=false ENTRYPOINT ["runmqserver"] +############################################################################### +# Build stage to build C code for custom authorization service (developer-only) +############################################################################### +FROM registry.redhat.io/rhel8/gcc-toolset-9-toolchain as cbuilder +# The URL to download the MQ installer from in tar.gz format +# This assumes an archive containing the MQ Non-Install packages +ARG MQ_URL +USER 0 +# Install the Apache Portable Runtime code (used for htpasswd hash checking) +RUN yum -y install apr-devel apr-util-openssl apr-util-devel +# Install MQ client +COPY install-mq.sh /usr/local/bin/ +RUN mkdir /opt/mqm \ + && chmod a+x /usr/local/bin/install-mq.sh \ + && sleep 1 \ + && INSTALL_SDK=1 install-mq.sh \ + && chown -R 1001:root /opt/mqm/* +COPY authservice/ /opt/app-root/src/authservice/ +WORKDIR /opt/app-root/src/authservice/mqhtpass +RUN make mqhtpass.so +RUN make htpass_test + ############################################################################### # Add default developer config ############################################################################### @@ -136,7 +157,7 @@ LABEL io.k8s.description="Simplify, accelerate and facilitate the reliable excha LABEL base-image=$BASE_IMAGE LABEL base-image-release=$BASE_TAG USER 0 -COPY --from=builder $GO_WORKDIR/amqpas* /opt/mqm/lib64/ +COPY --from=cbuilder /opt/app-root/src/authservice/mqhtpass/mqhtpass.so /opt/mqm/lib64/ COPY etc/mqm/*.ini /etc/mqm/ COPY etc/mqm/mq.htpasswd /etc/mqm/ RUN chmod 0660 /etc/mqm/mq.htpasswd diff --git a/authservice/mqhtpass/Makefile b/authservice/mqhtpass/Makefile new file mode 100644 index 0000000..f177175 --- /dev/null +++ b/authservice/mqhtpass/Makefile @@ -0,0 +1,42 @@ +# © Copyright IBM Corporation 2017, 2020 +# +# 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. + +# Where to find the sample test. +SRC_DIR = src + +# Flags passed to the C compiler. +CFLAGS += -std=c11 -fPIC -Wall + +log.o : $(SRC_DIR)/log.c $(SRC_DIR)/log.h + gcc $(CFLAGS) -c $(SRC_DIR)/log.c + +htpass.o : $(SRC_DIR)/htpass.c $(SRC_DIR)/htpass.h + gcc $(CFLAGS) -c -I /usr/include/apr-1 $^ + +htpass_test.o : htpass.o log.o + gcc $(CFLAGS) -I /usr/include/apr-1 -L/usr/lib64 -lapr-1 -laprutil-1 $(SRC_DIR)/htpass_test.c $^ -o htpass_test + +htpass_test : htpass_test.o + ./htpass_test + +mqhtpass.so : log.o htpass.o + gcc $(CFLAGS) -I/opt/mqm/inc -D_REENTRANT -L/usr/lib64 -lapr-1 -laprutil-1 -L/opt/mqm/lib64 -lmqm_r -Wl,-rpath,/opt/mqm/lib64 -Wl,-rpath,/usr/lib64 -shared $(SRC_DIR)/mqhtpass.c $^ -o $@ + ldd $@ + +mqhtpass_unittest.o : $(SRC_DIR)/mqhtpass_test.cc \ + $(SRC_DIR)/sample1.h $(FUSED_GTEST_H) + gcc $(CFLAGS) -c $(SRC_DIR)/mqhtpass_test.cc + +mqhtpass_unittest : mqhtpass.so mqhtpass_test.o gtest-all.o gtest_main.o + gcc $(CFLAGS) $^ -o $@ \ No newline at end of file diff --git a/authservice/mqhtpass/src/htpass.c b/authservice/mqhtpass/src/htpass.c new file mode 100644 index 0000000..477173f --- /dev/null +++ b/authservice/mqhtpass/src/htpass.c @@ -0,0 +1,155 @@ +/* +© Copyright IBM Corporation 2020 + +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 +#include +#include +#include +#include +#include "log.h" +#include +#include +#include +#include + +char * find_hash(char*, char*); + +char * find_hash(char *filename, char *user) +{ + bool found = false; + FILE *fp; + char *huser; + char *hash; + + fp = fopen(filename, "r"); + if (fp == NULL) + { + log_errorf("Error %d opening htpasswd file '%s'", errno, filename); + } + if (fp) + { + const size_t line_size = 1024; + char *line = malloc(line_size); + while (fgets(line, line_size, fp) != NULL) + { + huser = strtok(line, ":"); + if (strcmp(user, huser) == 0) + { + hash = strtok(NULL, " \r\n\t"); + found = true; + break; + } + } + fclose(fp); + // if (line) + // free(line); + // if (huser) + // free(huser); + // if (encPassword) + // free(encPassword); + } + if (!found) + { + hash = NULL; + } + return(hash); +} + +bool htpass_authenticate_user(char *filename, char *user, char *password) +{ + char *hash = find_hash(filename, user); + bool result = false; + // Use the Apache Portable Runtime utilities to validate the password against the hash. + // Supports multiple hashing algorithms, but we should only be using bcrypt + apr_status_t status = apr_password_validate(password, hash); + // status is usually either APR_SUCCESS or APR_EMISMATCH + if (status == APR_SUCCESS) { + result = true; + log_debugf("Correct password supplied. user=%s", user); + } else { + log_debugf("Incorrect password supplied. user=%s", user); + } + return(result); +} + +// bool htpass_authenticate_user(char *filename, char *user, char *password) +// { +// bool result = false; +// FILE *fp; +// // char line[1024]; +// char *huser; +// char *hash; +// // size_t len = 0; +// // size_t read; +// // int valid = -1; + +// fp = fopen(filename, "r"); +// if (fp == NULL) +// { +// log_errorf("Error %d opening htpasswd file '%s'", errno, filename); +// } +// if (fp) +// { +// const size_t line_size = 1024; +// char *line = malloc(line_size); +// while (fgets(line, line_size, fp) != NULL) +// { +// huser = strtok(line, ":"); +// if (strcmp(user, huser) == 0) +// { +// hash = strtok(NULL, " \r\n\t"); +// log_debugf("Matched user in htpasswd file: user=%s hash=%s*", huser, hash); + +// // Use the Apache Portable Runtime utilities to validate the password against the hash. +// // Supports multiple hashing algorithms, but we should only be using bcrypt +// apr_status_t status = apr_password_validate(password, hash); +// // status is usually either APR_SUCCESS or APR_EMISMATCH +// if (status == APR_SUCCESS) { +// result = true; +// log_debugf("Correct password supplied. user=%s", huser); +// } else { +// log_debugf("Incorrect password supplied. user=%s", huser); +// } +// // Break out of the loop, as we've found the right user +// break; +// // TODO: Do we need to free(hash)? +// } +// else +// { +// log_debugf("Read incorrect user in htpassword: user=%s", huser); +// } +// } +// fclose(fp); +// // if (line) +// // free(line); +// // if (huser) +// // free(huser); +// // if (encPassword) +// // free(encPassword); +// } +// return result; +// } + +bool htpass_valid_user(char *filename, char *user) +{ + char *hash = find_hash(filename, user); + bool valid = false; + if (hash != NULL) + { + valid = true; + } + return(valid); +} diff --git a/authservice/mqhtpass/src/htpass.h b/authservice/mqhtpass/src/htpass.h new file mode 100644 index 0000000..3ed6009 --- /dev/null +++ b/authservice/mqhtpass/src/htpass.h @@ -0,0 +1,23 @@ +/* +© Copyright IBM Corporation 2020 + +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 _HTPASS_H +#define _HTPASS_H + +_Bool htpass_authenticate_user(char *, char *, char *); +_Bool htpass_valid_user(char *, char *); + +#endif \ No newline at end of file diff --git a/authservice/mqhtpass/src/htpass_test.c b/authservice/mqhtpass/src/htpass_test.c new file mode 100644 index 0000000..73993d6 --- /dev/null +++ b/authservice/mqhtpass/src/htpass_test.c @@ -0,0 +1,71 @@ +/* +© Copyright IBM Corporation 2020 + +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 +#include +#include "log.h" +#include "htpass.h" + +void test_fail(const char *test_name) { + printf("Failed test %s\n", test_name); + exit(1); +} + +void test_htpass_authenticate_user_fred_valid() +{ + int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", "passw0rd"); + printf("%s: fred - %d\n", __func__, ok); + if (!ok) test_fail(__func__); +} + +void test_htpass_authenticate_user_fred_invalid1() +{ + int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", "passw0rd "); + printf("%s: fred - %d\n", __func__, ok); + if (ok) test_fail(__func__); +} + +void test_htpass_authenticate_user_fred_invalid2() +{ + int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", ""); + printf("%s: fred - %d\n", __func__, ok); + if (ok) test_fail(__func__); +} + +void test_htpass_authenticate_user_fred_invalid3() +{ + int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", "clearlywrong"); + printf("%s: fred - %d\n", __func__, ok); + if (ok) test_fail(__func__); +} + +void test_htpass_authenticate_user_barney_valid() +{ + int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "barney", "s3cret"); + printf("%s: barney - %d\n", __func__, ok); + if (!ok) test_fail(__func__); +} + +int main() +{ + log_init_file(stdout); + printf("TESTING BEGINS\n"); + test_htpass_authenticate_user_fred_valid(); + test_htpass_authenticate_user_fred_invalid1(); + test_htpass_authenticate_user_fred_invalid2(); + test_htpass_authenticate_user_fred_invalid3(); + test_htpass_authenticate_user_barney_valid(); +} \ No newline at end of file diff --git a/authservice/mqhtpass/src/htpass_test.htpasswd b/authservice/mqhtpass/src/htpass_test.htpasswd new file mode 100644 index 0000000..5c1399b --- /dev/null +++ b/authservice/mqhtpass/src/htpass_test.htpasswd @@ -0,0 +1,2 @@ +fred:$2y$05$3Fp9epsqEwWOHdyj9Ngf9.qfX34kzc9zNrdQ7kac0GmcCvQjIkAwy +barney:$2y$05$l8EoyCQ9y2PyfUzIDDfTyu7SSaJEYB1TuHy07xZvN7xt/pR3SIw0a diff --git a/authservice/mqhtpass/src/log.c b/authservice/mqhtpass/src/log.c new file mode 100644 index 0000000..d74608a --- /dev/null +++ b/authservice/mqhtpass/src/log.c @@ -0,0 +1,166 @@ +/* +© Copyright IBM Corporation 2020 + +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 +#include +#include +#include +#include + +#define LOG_LEVEL_INFO 0 +#define LOG_LEVEL_ERROR 1 +#define LOG_LEVEL_DEBUG 2 + + +FILE *fp = NULL; +int pid; + +int log_init(char *filename) +{ + int result = 0; + pid = getpid(); + if (!fp) + { + fp = fopen(filename, "a"); + if (fp) + { + setbuf(fp, NULL); + } + else + { + result = 1; + } + } + return result; +} + +void log_init_file(FILE *f) +{ + fp = f; +} + +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) + { + char buff[70]; + struct tm *utc; + time_t t; + + struct timeval now; + gettimeofday(&now, NULL); + t = now.tv_sec; + t = time(NULL); + utc = gmtime(&t); + + fprintf(fp, "{"); + fprintf(fp, "\"loglevel\":\"%s\"", level); + // Print ISO-8601 time and date + if (strftime(buff, sizeof buff, "%FT%T", utc)) + { + fprintf(fp, ", \"ibm_datetime\":\"%s.%3ld", buff, now.tv_usec); + } + fprintf(fp, ", \"ibm_processId\":\"%d\"", pid); + fprintf(fp, ", \"module\":\"%s:%d\"", source_file, source_line); + fprintf(fp, ", \"message\":\""); + + // Print log message, using varargs + va_list args; + va_start(args, format); + vfprintf(fp, format, args); + va_end(args); + fprintf(fp, "\"}\n"); + } +} + +/** + * Writes a message to the log file, using the specified type, based on a printf format string. + */ +// void log_printf(const char *level, const char *format, va_list args) +// { +// // FindSize(); +// if (fp) +// { +// char buff[70]; +// struct tm *utc; +// time_t t; + +// struct timeval now; +// gettimeofday(&now, NULL); +// t = now.tv_sec; + +// // Print ISO-8601 time and date +// t = time(NULL); +// utc = gmtime(&t); +// fprintf(fp, "{"); +// fprintf(fp, "\"loglevel\":\"%s\"", level); +// if (strftime(buff, sizeof buff, "%FT%T", utc)) +// { +// fprintf(fp, ", \"ibm_datetime\":\"%s.%3ld", buff, now.tv_usec); +// } +// fprintf(fp, ", \"ibm_processId\": \"%d\"", pid); +// fprintf(fp, ", \"message\":\""); + +// // Print log message, using varargs +// // va_list args; +// // va_start(args, format); +// vfprintf(fp, format, args); +// // va_end(args); +// fprintf(fp, "\"}\n"); +// } +// } + +// void log_errorf(const char *format, ...) +// { +// va_list args; +// va_start(args, format); +// log_printf("ERROR", format, args); +// va_end(args); +// } + +// void log_infof(const char *format, ...) +// { +// va_list args; +// va_start(args, format); +// log_printf("INFO", format, args); +// va_end(args); +// } + +// void log_debugf(const char *format, ...) +// { +// va_list args; +// va_start(args, format); +// log_printf("DEBUG", format, args); +// va_end(args); +// } + +// void log_debugf2(const char *source_file, const char *source_line, const char *format, ...) +// { +// va_list args; +// va_start(args, format); +// log_printf(source_line, format, args); +// va_end(args); +// } + diff --git a/authservice/mqhtpass/src/log.h b/authservice/mqhtpass/src/log.h new file mode 100644 index 0000000..40acf02 --- /dev/null +++ b/authservice/mqhtpass/src/log.h @@ -0,0 +1,50 @@ +/* +© Copyright IBM Corporation 2020 + +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 + +int log_init(char *); + +/** + * Initialize the log wih an existing file handle + */ +void log_init_file(FILE *); + +/** + * Write a message to the log file, based on a printf format string. + */ +void log_printf(const char*, int, const char*, const char*, ...); + +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__) + + +#endif \ No newline at end of file diff --git a/authservice/mqhtpass/src/mqhtpass.c b/authservice/mqhtpass/src/mqhtpass.c new file mode 100644 index 0000000..b39f938 --- /dev/null +++ b/authservice/mqhtpass/src/mqhtpass.c @@ -0,0 +1,262 @@ +/* +© Copyright IBM Corporation 2020 + +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 +#include +#include +#include +#include +#include "log.h" +#include "htpass.h" + +/****************************************************************************/ +/* Declare the internal functions that implement the interface */ +/****************************************************************************/ +MQZ_INIT_AUTHORITY MQStart; +static MQZ_AUTHENTICATE_USER mqhtpass_authenticate_user; +static MQZ_FREE_USER mqhtpass_free_user; +static MQZ_TERM_AUTHORITY mqhtpass_term_auth; + +#define LOG_FILE "/var/mqm/errors/mqhtpass.log" +#define HTPASSWD_FILE "/etc/mqm/mq.htpasswd" + +static char *trim(char *s); + +/** + * 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 (any other time - normally during the start of an agent + * process which is not necessarily the same as during MQCONN, especially + * when running multi-threaded agents) initialization, but there's + * nothing different that we'd want to do here based on that flag. + * + * Because of when the init function is called, there is no need to + * worry about multi-threaded stuff in this particular function. + */ +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; + + CC = log_init(LOG_FILE); + if (CC == MQCC_OK) + { + log_infof("MQStart options=%s qmgr=%s", ((Options == MQZIO_SECONDARY) ? "Secondary" : "Primary"), trim(QMgrName)); + } + /************************************************************************/ + /* Initialize the entry point vectors. This is performed for both */ + /* global and process initialisation, i.e whatever the value of the */ + /* Options field. */ + /************************************************************************/ + 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)mqhtpass_term_auth, &CC, &Reason); + + if (CC == MQCC_OK) + hc->MQZEP_Call(hc, MQZID_AUTHENTICATE_USER, (PMQFUNC)mqhtpass_authenticate_user, &CC, &Reason); + + if (CC == MQCC_OK) + hc->MQZEP_Call(hc, MQZID_FREE_USER, (PMQFUNC)mqhtpass_free_user, &CC, &Reason); + + *Version = MQZAS_VERSION_5; + *pCompCode = CC; + *pReason = Reason; + return; +} + +/** + * Called during the connection of any application. This allows the OAM + * to change the userid associated with the connection, regardless of the + * operating system user ID. One reason you might want to do that is to + * deal with non-standard user IDs, which perhaps are longer than 12 + * characters. The CorrelationPtr can be assigned in this function to + * point to some OAM-managed storage, and is available as part of the + * MQZED structure for all subsequent functions. Note that there is only + * one CorrelPtr stored for the user's hconn, so if two OAMs are chained + * and both want to manage storage for the connection, there would be + * difficulties as there is no reverse call that would allow the second + * to reset the first's pointer (or vice versa). I'd suggest instead + * using something like thread-specific storage as each thread is tied + * to the hconn. + * + * When a clntconn/svrconn channel connects to the queue manager, the + * authentication is supposed to take two stages. First as the + * channel program connects, and then as the MCAUSER is set. You will + * see this as "initial" and "change" context values in the parameters. + */ +static void MQENTRY mqhtpass_authenticate_user( + PMQCHAR pQMgrName, + PMQCSP pSecurityParms, + PMQZAC pApplicationContext, + PMQZIC pIdentityContext, + PMQPTR pCorrelationPtr, + PMQBYTE pComponentData, + PMQLONG pContinuation, + PMQLONG pCompCode, + PMQLONG pReason) +{ + char *spuser = NULL; + char *sppass = NULL; + + if ((pSecurityParms->AuthenticationType) == MQCSP_AUTH_USER_ID_AND_PWD) + { + // Authenticating a user ID and password. + + // Firstly, create null-terminated strings from the user credentials in the MQ CSP object + spuser = malloc(pSecurityParms->CSPUserIdLength + 1); + strncpy(spuser, pSecurityParms->CSPUserIdPtr, pSecurityParms->CSPUserIdLength); + spuser[pSecurityParms->CSPUserIdLength] = 0; + sppass = malloc(pSecurityParms->CSPPasswordLength + 1); + strncpy(sppass, pSecurityParms->CSPPasswordPtr, pSecurityParms->CSPPasswordLength); + sppass[pSecurityParms->CSPPasswordLength] = 0; + log_debugf("%s with CSP user set. user=%s", __func__, spuser); + bool authenticated = htpass_authenticate_user(HTPASSWD_FILE, spuser, sppass); + + if (authenticated) + { + *pCompCode = MQCC_OK; + *pReason = MQRC_NONE; + *pContinuation = MQZCI_CONTINUE; + memcpy(pIdentityContext->UserIdentifier, spuser, sizeof(pIdentityContext->UserIdentifier)); + log_debugf("Authenticated user=%s", pIdentityContext->UserIdentifier); + } + else + { + // Return a warning, which indicates to MQ that this authorization service hasn't + // authenticated the user, but other authorization services should be tried as well. + *pCompCode = MQCC_WARNING; + *pReason = MQRC_NONE; + *pContinuation = MQZCI_CONTINUE; + log_debugf( + "Failed to authenticate user=%s effuser=%s applname=%s cspuser=%s cc=%d reason=%d", + pIdentityContext->UserIdentifier, + pApplicationContext->EffectiveUserID, + pApplicationContext->ApplName, + spuser, + *pCompCode, + *pReason); + } + free(spuser); + free(sppass); + } + else + { + // Password not supplied, so just check that the user ID is valid + spuser = malloc(sizeof(PMQCHAR12)); + strncpy(spuser, pApplicationContext->EffectiveUserID, strlen(pApplicationContext->EffectiveUserID)); + spuser[sizeof(PMQCHAR12)] = 0; + log_debugf("%s without CSP user set. effectiveuid=%s", __func__, spuser); + bool valid_user = htpass_valid_user(HTPASSWD_FILE, spuser); + if (valid_user) + { + *pCompCode = MQCC_OK; + *pReason = MQRC_NONE; + *pContinuation = MQZCI_CONTINUE; + memcpy(pIdentityContext->UserIdentifier, spuser, sizeof(pIdentityContext->UserIdentifier)); + } + else + { + *pCompCode = MQCC_WARNING; + *pReason = MQRC_NONE; + *pContinuation = MQZCI_CONTINUE; + log_debugf( + "Invalid user=%s effuser=%s applname=%s cspuser=%s cc=%d reason=%d", + pIdentityContext->UserIdentifier, + pApplicationContext->EffectiveUserID, + pApplicationContext->ApplName, + spuser, + *pCompCode, + *pReason); + } + } + return; +} + +/** + * Called during MQDISC, as the inverse of the Authenticate. If the authorization + * service has allocated private storage to hold additional information about + * the user, then this is the time to free it. No more calls will be made + * to the authorization service for this connection instance of this user. + */ +static void MQENTRY mqhtpass_free_user( + PMQCHAR pQMgrName, + PMQZFP pFreeParms, + PMQBYTE pComponentData, + PMQLONG pContinuation, + + PMQLONG pCompCode, + PMQLONG pReason) +{ + log_infof("mqhtpass_freeuser()"); + *pCompCode = MQCC_WARNING; + *pReason = MQRC_NONE; + *pContinuation = MQZCI_CONTINUE; +} + +/** + * Called during MQDISC, as the inverse of the Authenticate. If the OAM + * has allocated private storage to hold additional information about + * the user, then this is the time to free it. No more calls will be made + * to the authorization service for this connection instance of this user. + */ +static void MQENTRY mqhtpass_term_auth( + MQHCONFIG hc, + MQLONG Options, + PMQCHAR pQMgrName, + PMQBYTE pComponentData, + PMQLONG pCompCode, + PMQLONG pReason) +{ + log_infof("mqhtpass_term_auth()"); + if ((Options & MQZTO_PRIMARY) == MQZTO_PRIMARY) + { + log_close(); + } + *pCompCode = MQCC_OK; + *pReason = MQRC_NONE; +} + +/** + * Remove trailing spaces from a string. + */ +static char *trim(char *s) +{ + int i; + for (i = strlen(s) - 1; i >= 0; i--) + { + if (s[i] == ' ') + s[i] = 0; + else + break; + } + return s; +} diff --git a/cmd/runmqdevserver/main.go b/cmd/runmqdevserver/main.go index 51e7163..d856e50 100644 --- a/cmd/runmqdevserver/main.go +++ b/cmd/runmqdevserver/main.go @@ -19,7 +19,6 @@ import ( "fmt" "io/ioutil" "os" - "os/exec" "syscall" "github.com/ibm-messaging/mq-container/internal/htpasswd" @@ -30,27 +29,6 @@ import ( var log *logger.Logger -func setPassword(user string, password string) error { - // #nosec G204 - cmd := exec.Command("sudo", "chpasswd") - stdin, err := cmd.StdinPipe() - if err != nil { - return err - } - fmt.Fprintf(stdin, "%s:%s", user, password) - err = stdin.Close() - if err != nil { - log.Errorf("Error closing password stdin: %v", err) - } - out, err := cmd.CombinedOutput() - if err != nil { - // Include the command output in the error - return fmt.Errorf("%v: %v", err.Error(), out) - } - log.Printf("Set password for \"%v\" user", user) - return nil -} - func getLogFormat() string { return os.Getenv("LOG_FORMAT") } diff --git a/etc/mqm/qm-service-component.ini b/etc/mqm/qm-service-component.ini index 7daf39c..a9bcd2b 100644 --- a/etc/mqm/qm-service-component.ini +++ b/etc/mqm/qm-service-component.ini @@ -1,7 +1,7 @@ ServiceComponent: Service=AuthorizationService Name=Dev.HtpAuth.Service - Module=/opt/mqm/lib64/amqpasdev.so + Module=/opt/mqm/lib64/mqhtpass.so ComponentDataSize=0 ServiceComponent: Service=AuthorizationService diff --git a/incubating/mqadvanced-server-dev/install-extra-packages.sh b/incubating/mqadvanced-server-dev/install-extra-packages.sh index 0535015..51d9ed2 100644 --- a/incubating/mqadvanced-server-dev/install-extra-packages.sh +++ b/incubating/mqadvanced-server-dev/install-extra-packages.sh @@ -25,17 +25,17 @@ test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false if ($UBUNTU); then export DEBIAN_FRONTEND=noninteractive apt-get update - apt-get install -y --no-install-recommends sudo + apt-get install -y --no-install-recommends libaprutil1 rm -rf /var/lib/apt/lists/* fi if ($YUM); then - yum -y install sudo + yum -y install apr-util-openssl yum -y clean all rm -rf /var/cache/yum/* fi if ($MICRODNF); then - microdnf install sudo + microdnf install apr-util-openssl microdnf clean all fi \ No newline at end of file diff --git a/internal/htpasswd/htpasswd.go b/internal/htpasswd/htpasswd.go index 0ef5091..e96fb84 100644 --- a/internal/htpasswd/htpasswd.go +++ b/internal/htpasswd/htpasswd.go @@ -110,44 +110,3 @@ func (htpfile mapHtPasswd) updateHtPasswordFile(isTest bool) error { } return ioutil.WriteFile(file, htpfile.GetBytes(), 0660) } - -// AuthenticateUser verifies if the given user password match with htpasswrd -func AuthenticateUser(user string, password string, isTest bool) (bool, bool, error) { - passwords := mapHtPasswd(map[string]string{}) - - if len(strings.TrimSpace(user)) == 0 || len(strings.TrimSpace(password)) == 0 { - return false, false, fmt.Errorf("UserId or Password are empty") - } - - err := passwords.ReadHtPasswordFile(isTest) - if err != nil { - return false, false, err - } - - ok := false - value, found := passwords[user] - - if !found { - return found, ok, fmt.Errorf("User not found in the mq.htpasswd file") - } - - err = bcrypt.CompareHashAndPassword([]byte(value), []byte(password)) - return found, err == nil, err -} - -// ValidateUser validates the given user -func ValidateUser(user string, isTest bool) (bool, error) { - passwords := mapHtPasswd(map[string]string{}) - - if len(strings.TrimSpace(user)) == 0 { - return false, fmt.Errorf("Userid is empty for AuthenticateUser") - } - - err := passwords.ReadHtPasswordFile(isTest) - if err != nil { - return false, err - } - - _, found := passwords[strings.TrimSpace(user)] - return found, nil -} diff --git a/internal/htpasswd/htpasswd_test.go b/internal/htpasswd/htpasswd_test.go deleted file mode 100644 index 00027c6..0000000 --- a/internal/htpasswd/htpasswd_test.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -© Copyright IBM Corporation 2020 - -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. -*/ -package htpasswd - -import ( - "testing" -) - -// TestCheckUser verifies Htpassword's use -func TestCheckUser(t *testing.T) { - err := SetPassword("guest", "guestpw", true) - if err != nil { - t.Fatalf("htpassword test failed due to error:%s\n", err.Error()) - } - found, ok, err := AuthenticateUser("guest", "guestpw", true) - if err != nil { - t.Fatalf("htpassword test1 failed as user could not be found:%s\n", err.Error()) - } - if found == false || ok == false { - t.Fatalf("htpassword test1 failed as user could not be found:%v, ok:%v\n", found, ok) - } - - found, ok, err = AuthenticateUser("myguest", "guestpw", true) - if err == nil { - t.Fatalf("htpassword test2 failed as no error received for non-existing user\n") - } - if found == true || ok == true { - t.Fatalf("htpassword test2 failed for non-existing user found :%v, ok:%v\n", found, ok) - } - - found, ok, err = AuthenticateUser("guest", "guest", true) - if err == nil { - t.Fatalf("htpassword test3 failed as incorrect password of user did not return error\n") - } - - if found == false || ok == true { - t.Fatalf("htpassword test3 failed for existing user with incorrect passwored found :%v, ok:%v\n", found, ok) - } - - found, err = ValidateUser("guest", true) - if err != nil || found == false { - t.Fatalf("htpassword test4 failed as user could not be found:%v, ok:%v\n", found, ok) - } - - found, err = ValidateUser("myguest", true) - if err != nil || found == true { - t.Fatalf("htpassword test5 failed as non-existing user returned to be found:%v, ok:%v\n", found, ok) - } -} diff --git a/internal/htpasswd/my.htpasswd b/internal/htpasswd/my.htpasswd deleted file mode 100644 index d9ce748..0000000 --- a/internal/htpasswd/my.htpasswd +++ /dev/null @@ -1 +0,0 @@ -guest:$2y$05$ifFP0nCmFed6.m4iB9CHRuHFps2YeeuwopmOvszWt0GRnN59p8qxW \ No newline at end of file diff --git a/internal/qmgrauth/pas.go b/internal/qmgrauth/pas.go deleted file mode 100644 index c6991cd..0000000 --- a/internal/qmgrauth/pas.go +++ /dev/null @@ -1,299 +0,0 @@ -/* -© Copyright IBM Corporation 2020 - -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. -package main - -/* -#cgo !windows CFLAGS: -I/opt/mqm/lib64 -D_REENTRANT -#cgo !windows,!darwin LDFLAGS: -L/opt/mqm/lib64 -lmqm_r -Wl,-rpath,/opt/mqm/lib64 -Wl,-rpath,/usr/lib64 -#cgo darwin LDFLAGS: -L/opt/mqm/lib64 -lmqm_r -Wl,-rpath,/opt/mqm/lib64 -Wl,-rpath,/usr/lib64 -#cgo windows CFLAGS: -I"C:/Program Files/IBM/MQ/Tools/c/include" -#cgo windows LDFLAGS: -L "C:/Program Files/IBM/MQ/bin64" -lmqm -#include -#include -#include -#include -#include -#include -#include -#include -static MQZ_INIT_AUTHORITY PASStart; -static MQZ_AUTHENTICATE_USER OAAuthUser; -static MQZ_FREE_USER OAFreeUser; -static MQZ_TERM_AUTHORITY OATermAuth; -extern int Authenticate(char *, char *); -extern int CheckAuthority(char *); -static char *OAEnvStr(MQLONG); -static void FindSize(); -static void PrintDateTime(); -static FILE *fp = NULL; -static int primary_process = 0; - -static void MQENTRY PASStart( - MQHCONFIG hc, - MQLONG Options, - MQCHAR48 QMgrName, - MQLONG ComponentDataLength, - PMQBYTE ComponentData, - PMQLONG Version, - PMQLONG pCompCode, - PMQLONG pReason) { - MQLONG CC = MQCC_OK; - MQLONG Reason = MQRC_NONE; - - if ((Options & MQZIO_PRIMARY) == MQZIO_PRIMARY) - primary_process = 1; - - fp=fopen("/var/mqm/errors/amqpasdev.log","a"); - - if (CC == MQCC_OK) - hc->MQZEP_Call(hc, MQZID_INIT_AUTHORITY,(PMQFUNC)PASStart,&CC,&Reason); - - if (CC == MQCC_OK) - hc->MQZEP_Call(hc,MQZID_TERM_AUTHORITY,(PMQFUNC)OATermAuth,&CC,&Reason); - - if (CC == MQCC_OK) - hc->MQZEP_Call(hc,MQZID_AUTHENTICATE_USER,(PMQFUNC)OAAuthUser,&CC,&Reason); - - if (CC == MQCC_OK) - hc->MQZEP_Call(hc,MQZID_FREE_USER,(PMQFUNC)OAFreeUser,&CC,&Reason); - - *Version = MQZAS_VERSION_5; - *pCompCode = CC; - *pReason = Reason; - - PrintDateTime(); - fprintf(fp, "Pluggable OAM Initialized.\n"); - fprintf(fp, "THIS IS A DEVELOPER ONLY CONFIGURATION AND NOT RECOMMENDED FOR PRODUCTION USAGE"); - return; -} - -static char *authuserfmt = - "\tUser : \"%12.12s\"\n"\ - "\tEffUser : \"%12.12s\"\n"\ - "\tAppName : \"%28.28s\"\n"\ - "\tApIdDt : \"%32.32s\"\n"\ - "\tEnv : \"%s\"\n"\ - "\tApp Pid : %d\n"\ - "\tApp Tid : %d\n"\ - ; -static void MQENTRY OAAuthUser ( - PMQCHAR pQMgrName, - PMQCSP pSecurityParms, - PMQZAC pApplicationContext, - PMQZIC pIdentityContext, - PMQPTR pCorrelationPtr, - PMQBYTE pComponentData, - PMQLONG pContinuation, - PMQLONG pCompCode, - PMQLONG pReason) -{ - char *spuser = NULL; - char *sppass = NULL; - int gorc = MQRC_NOT_AUTHORIZED; - - if ((pSecurityParms->CSPUserIdLength) > 0) { - //Grab the user creds from csp. - spuser = malloc(pSecurityParms->CSPUserIdLength+1); - strncpy(spuser,pSecurityParms->CSPUserIdPtr,pSecurityParms->CSPUserIdLength); - spuser[pSecurityParms->CSPUserIdLength]=0; - sppass = malloc(pSecurityParms->CSPPasswordLength+1); - strncpy(sppass,pSecurityParms->CSPPasswordPtr,pSecurityParms->CSPPasswordLength); - sppass[pSecurityParms->CSPPasswordLength]=0; - gorc = Authenticate(spuser,sppass); - - if (gorc == MQRC_NONE) { - *pCompCode = MQCC_OK; - *pReason = MQRC_NONE; - *pContinuation = MQZCI_CONTINUE; - memcpy( pIdentityContext->UserIdentifier - , spuser - , sizeof(pIdentityContext->UserIdentifier) ); - } else { - *pCompCode = MQCC_WARNING; - *pReason = MQRC_NONE; - *pContinuation = MQZCI_CONTINUE; - //we print to error file only if error'd - PrintDateTime(); - if (fp) { - fprintf(fp, authuserfmt, - pIdentityContext->UserIdentifier, - pApplicationContext->EffectiveUserID, - pApplicationContext->ApplName, - pIdentityContext->ApplIdentityData, - OAEnvStr(pApplicationContext->Environment), - pApplicationContext->ProcessId, - pApplicationContext->ThreadId); - - fprintf(fp,"\tCSP UserId : %s\n", spuser); - fprintf(fp,"\tCSP Password : %s\n", "****.."); - fprintf(fp,"\tPAS-Compcode:%d\n",*pCompCode); - fprintf(fp,"\tPAS-Reasoncode:%d\n",*pReason); - } - } - free(spuser); - free(sppass); - } else { - //this is only a normal UID authentication. - spuser = malloc(sizeof(PMQCHAR12)); - strncpy(spuser,pApplicationContext->EffectiveUserID,strlen(pApplicationContext->EffectiveUserID)); - spuser[sizeof(PMQCHAR12)]=0; - gorc = CheckAuthority(spuser); - if (gorc == MQRC_NONE){ - *pCompCode = MQCC_OK; - *pReason = MQRC_NONE; - *pContinuation = MQZCI_CONTINUE; - memcpy( pIdentityContext->UserIdentifier - , spuser - , sizeof(pIdentityContext->UserIdentifier) ); - } else { - *pCompCode = MQCC_WARNING; - *pReason = MQRC_NONE; - *pContinuation = MQZCI_CONTINUE; - //we print only if error'd - PrintDateTime(); - if (fp) - { - fprintf(fp, authuserfmt, - pIdentityContext->UserIdentifier, - pApplicationContext->EffectiveUserID, - pApplicationContext->ApplName, - pIdentityContext->ApplIdentityData, - OAEnvStr(pApplicationContext->Environment), - pApplicationContext->ProcessId, - pApplicationContext->ThreadId - ); - fprintf(fp,"\tUID : %s\n", spuser); - fprintf(fp,"\tPAS-Compcode:%d\n",*pCompCode); - fprintf(fp,"\tPAS-Reasoncode:%d\n",*pReason); - } - } - } - return; -} - -static void MQENTRY OAFreeUser ( - PMQCHAR pQMgrName, - PMQZFP pFreeParms, - PMQBYTE pComponentData, - PMQLONG pContinuation, - - PMQLONG pCompCode, - PMQLONG pReason) -{ - *pCompCode = MQCC_WARNING; - *pReason = MQRC_NONE; - *pContinuation = MQZCI_CONTINUE; - return; -} - -static void MQENTRY OATermAuth( - MQHCONFIG hc, - MQLONG Options, - PMQCHAR pQMgrName, - PMQBYTE pComponentData, - PMQLONG pCompCode, - PMQLONG pReason) -{ - if ((primary_process) && ((Options & MQZTO_PRIMARY) == MQZTO_PRIMARY) || - ((Options & MQZTO_SECONDARY) == MQZTO_SECONDARY)) - { - if (fp) - { - fclose(fp); - fp = NULL; - } - } - *pCompCode = MQCC_OK; - *pReason = MQRC_NONE; -} - -static void PrintDateTime() { - FindSize(); - struct tm *local; - time_t t; - t = time(NULL); - local = localtime(&t); - if (fp) { - fprintf(fp, "-------------------------------------------------\n"); - fprintf(fp, "Local time: %s", asctime(local)); - local = gmtime(&t); - fprintf(fp, "UTC time: %s", asctime(local)); - } - return; -} - -static char *OAEnvStr(MQLONG x) - { - switch (x) - { - case MQXE_OTHER: return "Application"; - case MQXE_MCA: return "Channel"; - case MQXE_MCA_SVRCONN: return "Channel SvrConn"; - case MQXE_COMMAND_SERVER: return "Command Server"; - case MQXE_MQSC: return "MQSC"; - default: return "Invalid Environment"; - } - } - -static void FindSize() -{ - int sz = 0; - int prev=ftell(fp); - fseek(fp, 0L, SEEK_END); - sz=ftell(fp); - //if log file size goes over 1mb, rewind it. - if (sz > 1000000) { - rewind(fp); - } else { - fseek(fp, prev, SEEK_SET); - } -} - -*/ -import "C" -import "github.com/ibm-messaging/mq-container/internal/htpasswd" - -//export MQStart -func MQStart(hc C.MQHCONFIG, Options C.MQLONG, QMgrName C.PMQCHAR, ComponentDataLength C.MQLONG, ComponentData C.PMQBYTE, Version C.PMQLONG, pCompCode C.PMQLONG, pReason C.PMQLONG) { - C.PASStart(hc, Options, QMgrName, ComponentDataLength, ComponentData, Version, pCompCode, pReason) -} - -//export Authenticate -func Authenticate(x *C.char, y *C.char) C.int { - user := C.GoString(x) - pwd := C.GoString(y) - found, ok, err := htpasswd.AuthenticateUser(user, pwd, false) - - if !found || !ok || err != nil { - return C.MQRC_UNKNOWN_OBJECT_NAME - } - return C.MQRC_NONE -} - -//export CheckAuthority -func CheckAuthority(x *C.char) C.int { - user := C.GoString(x) - found, err := htpasswd.ValidateUser(user, false) - if !found || err != nil { - return C.MQRC_UNKNOWN_OBJECT_NAME - } - return C.MQRC_NONE - -} - -func main() {}