From 99a1e4aa7477cb3e920fb094ab279b45c4492829 Mon Sep 17 00:00:00 2001 From: David Bell Date: Wed, 20 Jan 2021 10:37:09 +0000 Subject: [PATCH 01/10] moving to bionic --- .travis.yml | 4 ++-- travis-build-scripts/install-credential-helper.sh | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2952fc7..e37a3c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -dist: xenial +dist: bionic +group: beta sudo: required language: go -group: xeniallegacy go: - "1.13.15" diff --git a/travis-build-scripts/install-credential-helper.sh b/travis-build-scripts/install-credential-helper.sh index 14b9021..0fba443 100755 --- a/travis-build-scripts/install-credential-helper.sh +++ b/travis-build-scripts/install-credential-helper.sh @@ -27,7 +27,7 @@ make pass cp bin/docker-credential-pass $GOPATH/bin/docker-credential-pass mkdir -p /home/travis/.docker echo '{ "credsStore": "pass" }' | tee /home/travis/.docker/config.json -gpg --batch --gen-key <<-EOF +gpg2 --batch --gen-key <<-EOF %echo generating a standard key Key-Type: DSA Key-Length: 1024 @@ -36,13 +36,14 @@ Subkey-Length: 1024 Name-Real: Travis CI Name-Email: travis@osism.io Expire-Date: 0 +Passphrase: $REGISTRY_PASS %commit %echo done EOF -key=$(gpg --no-auto-check-trustdb --list-secret-keys | grep ^sec | cut -d/ -f2 | cut -d" " -f1) -gpg --export-secret-keys | gpg2 --import - +key=$(gpg2 --list-secret-keys | grep uid -B 1 | head -n 1 | sed 's/^ *//g') pass init $key pass insert docker-credential-helpers/docker-pass-initialized-check <<-EOF pass is initialized pass is initialized EOF +gpg2 --passphrase $REGISTRY_PASS --pinentry-mode=loopback --output doc --decrypt ~/.password-store/docker-credential-helpers/docker-pass-initialized-check.gpg From 5fd9fc5e26743f7f58203087dbbb210aee523311 Mon Sep 17 00:00:00 2001 From: Arthur Barr Date: Thu, 1 Oct 2020 17:15:37 +0100 Subject: [PATCH 02/10] Convert HTPasswd code from Go to C --- Dockerfile-server | 25 +- authservice/mqhtpass/Makefile | 42 +++ authservice/mqhtpass/src/htpass.c | 155 +++++++++ authservice/mqhtpass/src/htpass.h | 23 ++ authservice/mqhtpass/src/htpass_test.c | 71 +++++ authservice/mqhtpass/src/htpass_test.htpasswd | 2 + authservice/mqhtpass/src/log.c | 166 ++++++++++ authservice/mqhtpass/src/log.h | 50 +++ authservice/mqhtpass/src/mqhtpass.c | 262 +++++++++++++++ cmd/runmqdevserver/main.go | 22 -- etc/mqm/qm-service-component.ini | 2 +- .../install-extra-packages.sh | 6 +- internal/htpasswd/htpasswd.go | 41 --- internal/htpasswd/htpasswd_test.go | 62 ---- internal/htpasswd/my.htpasswd | 1 - internal/qmgrauth/pas.go | 299 ------------------ 16 files changed, 798 insertions(+), 431 deletions(-) create mode 100644 authservice/mqhtpass/Makefile create mode 100644 authservice/mqhtpass/src/htpass.c create mode 100644 authservice/mqhtpass/src/htpass.h create mode 100644 authservice/mqhtpass/src/htpass_test.c create mode 100644 authservice/mqhtpass/src/htpass_test.htpasswd create mode 100644 authservice/mqhtpass/src/log.c create mode 100644 authservice/mqhtpass/src/log.h create mode 100644 authservice/mqhtpass/src/mqhtpass.c delete mode 100644 internal/htpasswd/htpasswd_test.go delete mode 100644 internal/htpasswd/my.htpasswd delete mode 100644 internal/qmgrauth/pas.go 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() {} From 4257f6a199114764bc0359cf90a5fc13052d9447 Mon Sep 17 00:00:00 2001 From: Arthur Barr Date: Wed, 18 Nov 2020 10:05:03 +0000 Subject: [PATCH 03/10] Improvements to htpasswd code following review Improved multi-threading, including new test --- Dockerfile-server | 5 +- authservice/mqhtpass/Makefile | 48 +++++---- authservice/mqhtpass/src/htpass.c | 92 ++++------------- authservice/mqhtpass/src/htpass_test.c | 132 +++++++++++++++++++++++-- authservice/mqhtpass/src/log.c | 130 +++++++++--------------- authservice/mqhtpass/src/log.h | 5 +- authservice/mqhtpass/src/mqhtpass.c | 60 +++++++---- 7 files changed, 262 insertions(+), 210 deletions(-) diff --git a/Dockerfile-server b/Dockerfile-server index e49dceb..10e2fbb 100644 --- a/Dockerfile-server +++ b/Dockerfile-server @@ -133,8 +133,7 @@ RUN mkdir /opt/mqm \ && 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 +RUN make all ############################################################################### # Add default developer config @@ -157,7 +156,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=cbuilder /opt/app-root/src/authservice/mqhtpass/mqhtpass.so /opt/mqm/lib64/ +COPY --from=cbuilder /opt/app-root/src/authservice/mqhtpass/build/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 index f177175..01939f1 100644 --- a/authservice/mqhtpass/Makefile +++ b/authservice/mqhtpass/Makefile @@ -12,31 +12,39 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Where to find the sample test. +# This Makefile expects the following to be installed: +# - gcc +# - ldd +# - MQ SDK (mqm_r library, plus header files) +# - Apache Portable Runtime (apr-1 and aprutil-1 libraries, plus header files) + SRC_DIR = src +BUILD_DIR = ./build -# Flags passed to the C compiler. -CFLAGS += -std=c11 -fPIC -Wall +# Flags passed to the C compiler. Need to use gnu11 to get POSIX functions needed for file locking. +CFLAGS += -std=gnu11 -fPIC -Wall -m64 -log.o : $(SRC_DIR)/log.c $(SRC_DIR)/log.h - gcc $(CFLAGS) -c $(SRC_DIR)/log.c +LIB_APR = -L/usr/lib64 -lapr-1 -laprutil-1 +LIB_MQ = -L/opt/mqm/lib64 -lmqm_r -htpass.o : $(SRC_DIR)/htpass.c $(SRC_DIR)/htpass.h - gcc $(CFLAGS) -c -I /usr/include/apr-1 $^ +all: $(BUILD_DIR)/mqhtpass.so $(BUILD_DIR)/htpass_test -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 +$(BUILD_DIR)/log.o : $(SRC_DIR)/log.c $(SRC_DIR)/log.h + mkdir -p ${dir $@} + gcc $(CFLAGS) -c $(SRC_DIR)/log.c -o $@ -htpass_test : htpass_test.o - ./htpass_test +$(BUILD_DIR)/htpass.o : $(SRC_DIR)/htpass.c $(SRC_DIR)/htpass.h + mkdir -p ${dir $@} + gcc $(CFLAGS) -c $(SRC_DIR)/htpass.c -I /usr/include/apr-1 -o $@ -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 $@ +$(BUILD_DIR)/htpass_test : $(BUILD_DIR)/htpass.o $(BUILD_DIR)/log.o + mkdir -p ${dir $@} + gcc $(CFLAGS) $(LIB_APR) -lpthread $(SRC_DIR)/htpass_test.c $^ -o $@ +# Run HTPasswd tests, and print log if they fail + $@ || (cat htpass_test*.log && exit 1) + +$(BUILD_DIR)/mqhtpass.so : $(BUILD_DIR)/log.o $(BUILD_DIR)/htpass.o + mkdir -p ${dir $@} + # NOTE: rpath for libapr will be different on Ubuntu + gcc $(CFLAGS) -I/opt/mqm/inc -D_REENTRANT $(LIB_APR) $(LIB_MQ) -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 index 477173f..6b00ecb 100644 --- a/authservice/mqhtpass/src/htpass.c +++ b/authservice/mqhtpass/src/htpass.c @@ -25,9 +25,9 @@ limitations under the License. #include #include -char * find_hash(char*, char*); +char *find_hash(char *, char *); -char * find_hash(char *filename, char *user) +char *find_hash(char *filename, char *user) { bool found = false; FILE *fp; @@ -45,27 +45,26 @@ char * find_hash(char *filename, char *user) char *line = malloc(line_size); while (fgets(line, line_size, fp) != NULL) { - huser = strtok(line, ":"); - if (strcmp(user, huser) == 0) + char *saveptr; + // Need to use strtok_r to be safe for multiple threads + huser = strtok_r(line, ":", &saveptr); + if (huser && (strcmp(user, huser) == 0)) { - hash = strtok(NULL, " \r\n\t"); + // Make a duplicate of the string, because we'll be keeping it + hash = strdup(strtok_r(NULL, " \r\n\t", &saveptr)); found = true; break; } } fclose(fp); - // if (line) - // free(line); - // if (huser) - // free(huser); - // if (encPassword) - // free(encPassword); + if (line) + free(line); } if (!found) { hash = NULL; } - return(hash); + return (hash); } bool htpass_authenticate_user(char *filename, char *user, char *password) @@ -76,73 +75,18 @@ bool htpass_authenticate_user(char *filename, char *user, char *password) // 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) { + if (status == APR_SUCCESS) + { result = true; log_debugf("Correct password supplied. user=%s", user); - } else { + } + else + { log_debugf("Incorrect password supplied. user=%s", user); } - return(result); + 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); @@ -151,5 +95,5 @@ bool htpass_valid_user(char *filename, char *user) { valid = true; } - return(valid); + return (valid); } diff --git a/authservice/mqhtpass/src/htpass_test.c b/authservice/mqhtpass/src/htpass_test.c index 73993d6..7cdf0a1 100644 --- a/authservice/mqhtpass/src/htpass_test.c +++ b/authservice/mqhtpass/src/htpass_test.c @@ -14,58 +14,172 @@ See the License for the specific language governing permissions and limitations under the License. */ +#include #include #include #include "log.h" #include "htpass.h" -void test_fail(const char *test_name) { - printf("Failed test %s\n", test_name); +// Headers for multi-threaded tests +#include + +// 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 tests +// ---------------------------------------------------------------------------- + void test_htpass_authenticate_user_fred_valid() { + test_start(); int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", "passw0rd"); printf("%s: fred - %d\n", __func__, ok); - if (!ok) test_fail(__func__); + if (!ok) + test_fail(__func__); + test_pass(); } void test_htpass_authenticate_user_fred_invalid1() { + test_start(); int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", "passw0rd "); printf("%s: fred - %d\n", __func__, ok); - if (ok) test_fail(__func__); + if (ok) + test_fail(__func__); + test_pass(); } void test_htpass_authenticate_user_fred_invalid2() { + test_start(); int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", ""); printf("%s: fred - %d\n", __func__, ok); - if (ok) test_fail(__func__); + if (ok) + test_fail(__func__); + test_pass(); } void test_htpass_authenticate_user_fred_invalid3() { + test_start(); int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", "clearlywrong"); printf("%s: fred - %d\n", __func__, ok); - if (ok) test_fail(__func__); + if (ok) + test_fail(__func__); + test_pass(); } void test_htpass_authenticate_user_barney_valid() { + test_start(); int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "barney", "s3cret"); printf("%s: barney - %d\n", __func__, ok); - if (!ok) test_fail(__func__); + if (!ok) + 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) +{ + for (int i = 0; i < NUM_TESTS_PER_THREAD; i++) + { + int ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "barney", "s3cret"); + if (!ok) + test_fail(__func__); + ok = htpass_authenticate_user("./src/htpass_test.htpasswd", "fred", "passw0rd"); + if (!ok) + 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++; + } + } + fclose(log); +} + +// Test authenticate_user with multiple threads, each doing many authentications +void test_htpass_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(); +} + +// ---------------------------------------------------------------------------- + int main() { - log_init_file(stdout); - printf("TESTING BEGINS\n"); + // Turn on debugging for the tests + setenv("DEBUG", "true", true); + log_init("htpass_test.log"); 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(); + log_close(); + + // Call multi-threaded test last, because it re-initializes the log to use a file + test_htpass_authenticate_user_multithreaded("htpass_test_multithreaded.log"); } \ No newline at end of file diff --git a/authservice/mqhtpass/src/log.c b/authservice/mqhtpass/src/log.c index d74608a..047fb96 100644 --- a/authservice/mqhtpass/src/log.c +++ b/authservice/mqhtpass/src/log.c @@ -14,19 +14,33 @@ See the License for the specific language governing permissions and limitations under the License. */ +#include #include +#include #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; +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; + } + } +} int log_init(char *filename) { @@ -44,12 +58,14 @@ int log_init(char *filename) result = 1; } } + init_debug(); return result; } void log_init_file(FILE *f) { fp = f; + init_debug(); } void log_close() @@ -65,102 +81,46 @@ void log_printf(const char *source_file, int source_line, const char *level, con { if (fp) { - char buff[70]; + // 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); - fprintf(fp, "{"); - fprintf(fp, "\"loglevel\":\"%s\"", level); + cur += snprintf(cur, end-cur, "{"); + cur += snprintf(cur, end-cur, "\"loglevel\":\"%s\"", level); // Print ISO-8601 time and date - if (strftime(buff, sizeof buff, "%FT%T", utc)) + if (strftime(date_buf, sizeof date_buf, "%FT%T", utc)) { - fprintf(fp, ", \"ibm_datetime\":\"%s.%3ld", buff, now.tv_usec); + // Round microseconds down to milliseconds, for consistency + cur += snprintf(cur, end-cur, ", \"ibm_datetime\":\"%s.%03ldZ", date_buf, now.tv_usec / 1000); } - fprintf(fp, ", \"ibm_processId\":\"%d\"", pid); - fprintf(fp, ", \"module\":\"%s:%d\"", source_file, source_line); - fprintf(fp, ", \"message\":\""); + cur += snprintf(cur, end-cur, ", \"ibm_processId\":\"%d\"", pid); + cur += snprintf(cur, end-cur, ", \"module\":\"%s:%d\"", source_file, source_line); + cur += snprintf(cur, end-cur, ", \"message\":\""); // Print log message, using varargs va_list args; va_start(args, format); - vfprintf(fp, format, args); + cur += vsnprintf(cur, end-cur, format, args); va_end(args); - fprintf(fp, "\"}\n"); + 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, buf); } } -/** - * 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 index 40acf02..e3fc0cf 100644 --- a/authservice/mqhtpass/src/log.h +++ b/authservice/mqhtpass/src/log.h @@ -17,10 +17,13 @@ limitations under the License. #ifndef _LOG_H #define _LOG_H +/** + * Initialize the log to use the given file name. + */ int log_init(char *); /** - * Initialize the log wih an existing file handle + * Initialize the log with an existing file handle. */ void log_init_file(FILE *); diff --git a/authservice/mqhtpass/src/mqhtpass.c b/authservice/mqhtpass/src/mqhtpass.c index b39f938..cca4456 100644 --- a/authservice/mqhtpass/src/mqhtpass.c +++ b/authservice/mqhtpass/src/mqhtpass.c @@ -64,12 +64,16 @@ void MQENTRY MQStart( { MQLONG CC = MQCC_OK; MQLONG Reason = MQRC_NONE; - - CC = log_init(LOG_FILE); - if (CC == MQCC_OK) + int log_rc = 0; + + log_rc = log_init(LOG_FILE); + if (log_rc != 0) { - log_infof("MQStart options=%s qmgr=%s", ((Options == MQZIO_SECONDARY) ? "Secondary" : "Primary"), trim(QMgrName)); + CC = MQCC_FAILED; + Reason = MQRC_INITIALIZATION_FAILED; } + + 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 */ @@ -126,6 +130,13 @@ static void MQENTRY mqhtpass_authenticate_user( { char *spuser = NULL; char *sppass = 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) { @@ -133,9 +144,21 @@ static void MQENTRY mqhtpass_authenticate_user( // Firstly, create null-terminated strings from the user credentials in the MQ CSP object spuser = malloc(pSecurityParms->CSPUserIdLength + 1); + if (!spuser) + { + log_errorf("Unable to allocate memory"); + return; + } strncpy(spuser, pSecurityParms->CSPUserIdPtr, pSecurityParms->CSPUserIdLength); spuser[pSecurityParms->CSPUserIdLength] = 0; sppass = malloc(pSecurityParms->CSPPasswordLength + 1); + if (!sppass) + { + log_errorf("Unable to allocate memory"); + if (spuser) + free(spuser); + return; + } strncpy(sppass, pSecurityParms->CSPPasswordPtr, pSecurityParms->CSPPasswordLength); sppass[pSecurityParms->CSPPasswordLength] = 0; log_debugf("%s with CSP user set. user=%s", __func__, spuser); @@ -151,11 +174,6 @@ static void MQENTRY mqhtpass_authenticate_user( } 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, @@ -165,13 +183,20 @@ static void MQENTRY mqhtpass_authenticate_user( *pCompCode, *pReason); } - free(spuser); - free(sppass); + if (spuser) + free(spuser); + if (sppass) + free(sppass); } else { // Password not supplied, so just check that the user ID is valid - spuser = malloc(sizeof(PMQCHAR12)); + spuser = malloc(sizeof(PMQCHAR12) + 1); + if (!sppass) + { + log_errorf("Unable to allocate memory"); + return; + } strncpy(spuser, pApplicationContext->EffectiveUserID, strlen(pApplicationContext->EffectiveUserID)); spuser[sizeof(PMQCHAR12)] = 0; log_debugf("%s without CSP user set. effectiveuid=%s", __func__, spuser); @@ -185,9 +210,6 @@ static void MQENTRY mqhtpass_authenticate_user( } 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, @@ -197,6 +219,8 @@ static void MQENTRY mqhtpass_authenticate_user( *pCompCode, *pReason); } + if (spuser) + free(spuser); } return; } @@ -216,7 +240,7 @@ static void MQENTRY mqhtpass_free_user( PMQLONG pCompCode, PMQLONG pReason) { - log_infof("mqhtpass_freeuser()"); + log_debugf("mqhtpass_freeuser()"); *pCompCode = MQCC_WARNING; *pReason = MQRC_NONE; *pContinuation = MQZCI_CONTINUE; @@ -236,8 +260,8 @@ static void MQENTRY mqhtpass_term_auth( PMQLONG pCompCode, PMQLONG pReason) { - log_infof("mqhtpass_term_auth()"); - if ((Options & MQZTO_PRIMARY) == MQZTO_PRIMARY) + log_debugf("mqhtpass_term_auth()"); + if (Options == MQZTO_PRIMARY) { log_close(); } From ac3dcdd0d076118fa646b9a5a58acfabaaab5f3c Mon Sep 17 00:00:00 2001 From: Arthur Barr Date: Wed, 9 Dec 2020 14:52:40 +0000 Subject: [PATCH 04/10] Further changes with htpasswd provider --- authservice/mqhtpass/src/htpass.c | 70 ++++++-- authservice/mqhtpass/src/htpass.h | 25 ++- authservice/mqhtpass/src/htpass_test.c | 29 ++- .../mqhtpass/src/htpass_test_invalid.htpasswd | 3 + authservice/mqhtpass/src/log.c | 26 ++- authservice/mqhtpass/src/log.h | 16 +- authservice/mqhtpass/src/mqhtpass.c | 167 ++++++++++-------- cmd/runmqserver/logging.go | 9 +- cmd/runmqserver/main.go | 7 + docs/pluggable-connauth.md | 4 +- docs/security.md | 15 -- .../install-extra-packages.sh | 4 +- install-mq-server-prereqs.sh | 4 +- 13 files changed, 258 insertions(+), 121 deletions(-) create mode 100644 authservice/mqhtpass/src/htpass_test_invalid.htpasswd diff --git a/authservice/mqhtpass/src/htpass.c b/authservice/mqhtpass/src/htpass.c index 6b00ecb..c34ddd7 100644 --- a/authservice/mqhtpass/src/htpass.c +++ b/authservice/mqhtpass/src/htpass.c @@ -25,7 +25,44 @@ limitations under the License. #include #include -char *find_hash(char *, char *); +bool htpass_valid_file(char *filename) +{ + bool valid = true; + FILE *fp; + char *huser; + + 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) + { + char *saveptr; + // Need to use strtok_r to be safe for multiple threads + huser = strtok_r(line, ":", &saveptr); + if (strlen(huser) >= 12) + { + log_errorf("Invalid htpasswd file for use with IBM MQ. User '%s' is longer than twelve characters", huser); + valid = false; + break; + } + else { + + } + } + fclose(fp); + if (line) + { + free(line); + } + } + return valid; +} char *find_hash(char *filename, char *user) { @@ -58,33 +95,42 @@ char *find_hash(char *filename, char *user) } fclose(fp); if (line) + { free(line); + } } if (!found) { hash = NULL; } - return (hash); + 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) + if (hash == NULL) { - result = true; - log_debugf("Correct password supplied. user=%s", user); + log_debugf("User does not exist. user=%s", user); } else { - log_debugf("Incorrect password supplied. user=%s", user); + // 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); + return result; } bool htpass_valid_user(char *filename, char *user) @@ -95,5 +141,5 @@ bool htpass_valid_user(char *filename, char *user) { valid = true; } - return (valid); + return valid; } diff --git a/authservice/mqhtpass/src/htpass.h b/authservice/mqhtpass/src/htpass.h index 3ed6009..a519702 100644 --- a/authservice/mqhtpass/src/htpass.h +++ b/authservice/mqhtpass/src/htpass.h @@ -17,7 +17,28 @@ limitations under the License. #ifndef _HTPASS_H #define _HTPASS_H -_Bool htpass_authenticate_user(char *, char *, char *); -_Bool htpass_valid_user(char *, char *); +/** + * Validate an HTPasswd file for use with IBM MQ. + * + * @param filename the HTPasswd file + */ +_Bool htpass_valid_file(char *filename); + +/** + * Authenticate a user, based on the supplied file name. + * + * @param filename the HTPasswd file + * @param user the user name to authenticate + * @param password the password of the user + */ +_Bool htpass_authenticate_user(char *filename, char *user, char *password); + +/** + * Validate that a user exists in the password file. + * + * @param filename the HTPasswd file + * @param user the user name to validate + */ +_Bool htpass_valid_user(char *filename, char *user); #endif \ No newline at end of file diff --git a/authservice/mqhtpass/src/htpass_test.c b/authservice/mqhtpass/src/htpass_test.c index 7cdf0a1..8879783 100644 --- a/authservice/mqhtpass/src/htpass_test.c +++ b/authservice/mqhtpass/src/htpass_test.c @@ -36,9 +36,30 @@ void test_fail(const char *test_name) exit(1); } +// ---------------------------------------------------------------------------- +// Simple tests for file validation +// ---------------------------------------------------------------------------- + +void test_htpass_valid_file_ok() +{ + test_start(); + int ok = htpass_valid_file("./src/htpass_test.htpasswd"); + if (!ok) + test_fail(__func__); + test_pass(); +} + +void test_htpass_valid_file_too_long() +{ + test_start(); + int ok = htpass_valid_file("./src/htpass_test_invalid.htpasswd"); + if (ok) + test_fail(__func__); + test_pass(); +} // ---------------------------------------------------------------------------- -// Simple tests +// Simple tests for authentication // ---------------------------------------------------------------------------- void test_htpass_authenticate_user_fred_valid() @@ -136,6 +157,10 @@ void check_log_file_valid(char *filename) errors++; } } + if (line) + { + free(line); + } fclose(log); } @@ -173,6 +198,8 @@ int main() // Turn on debugging for the tests setenv("DEBUG", "true", true); log_init("htpass_test.log"); + test_htpass_valid_file_ok(); + test_htpass_valid_file_too_long(); test_htpass_authenticate_user_fred_valid(); test_htpass_authenticate_user_fred_invalid1(); test_htpass_authenticate_user_fred_invalid2(); diff --git a/authservice/mqhtpass/src/htpass_test_invalid.htpasswd b/authservice/mqhtpass/src/htpass_test_invalid.htpasswd new file mode 100644 index 0000000..4a01c74 --- /dev/null +++ b/authservice/mqhtpass/src/htpass_test_invalid.htpasswd @@ -0,0 +1,3 @@ +fred:$2y$05$3Fp9epsqEwWOHdyj9Ngf9.qfX34kzc9zNrdQ7kac0GmcCvQjIkAwy +barney:$2y$05$l8EoyCQ9y2PyfUzIDDfTyu7SSaJEYB1TuHy07xZvN7xt/pR3SIw0a +namewhichisfartoolongformq:$2y$05$l8EoyCQ9y2PyfUzIDDfTyu7SSaJEYB1TuHy07xZvN7xt/pR3SIw0a \ No newline at end of file diff --git a/authservice/mqhtpass/src/log.c b/authservice/mqhtpass/src/log.c index 047fb96..773cea6 100644 --- a/authservice/mqhtpass/src/log.c +++ b/authservice/mqhtpass/src/log.c @@ -42,7 +42,10 @@ void init_debug(){ } } -int log_init(char *filename) +/** + * 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(); @@ -51,6 +54,7 @@ int log_init(char *filename) fp = fopen(filename, "a"); if (fp) { + // Disable buffering for this file setbuf(fp, NULL); } else @@ -62,6 +66,18 @@ int log_init(char *filename) 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; @@ -105,12 +121,18 @@ void log_printf(const char *source_file, int source_line, const char *level, con 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 / 1000); + cur += snprintf(cur, end-cur, ", \"ibm_datetime\":\"%s.%03ldZ\"", date_buf, now.tv_usec / 1000); } cur += snprintf(cur, end-cur, ", \"ibm_processId\":\"%d\"", pid); 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, "mqhtpass: "); + } + // Print log message, using varargs va_list args; va_start(args, format); diff --git a/authservice/mqhtpass/src/log.h b/authservice/mqhtpass/src/log.h index e3fc0cf..a0527e8 100644 --- a/authservice/mqhtpass/src/log.h +++ b/authservice/mqhtpass/src/log.h @@ -17,20 +17,30 @@ 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 *); +int log_init(char *filename); /** * Initialize the log with an existing file handle. */ -void log_init_file(FILE *); +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*, int, const char*, const char*, ...); +void log_printf(const char *source_file, int source_line, const char *level, const char *format, ...); void log_close(); diff --git a/authservice/mqhtpass/src/mqhtpass.c b/authservice/mqhtpass/src/mqhtpass.c index cca4456..0076661 100644 --- a/authservice/mqhtpass/src/mqhtpass.c +++ b/authservice/mqhtpass/src/mqhtpass.c @@ -24,16 +24,15 @@ limitations under the License. #include "log.h" #include "htpass.h" -/****************************************************************************/ -/* Declare the internal functions that implement the interface */ -/****************************************************************************/ +// 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; +static MQZ_TERM_AUTHORITY mqhtpass_terminate; -#define LOG_FILE "/var/mqm/errors/mqhtpass.log" +#define LOG_FILE "/var/mqm/errors/mqhtpass.json" #define HTPASSWD_FILE "/etc/mqm/mq.htpasswd" +#define NAME "MQ Advanced for Developers custom authentication service" static char *trim(char *s); @@ -44,13 +43,7 @@ static char *trim(char *s); * * 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. + * SECONDARY. */ void MQENTRY MQStart( MQHCONFIG hc, @@ -65,32 +58,53 @@ void MQENTRY MQStart( MQLONG CC = MQCC_OK; MQLONG Reason = MQRC_NONE; int log_rc = 0; - - log_rc = log_init(LOG_FILE); + + 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; } - 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 (Options == MQZIO_PRIMARY) + { + log_infof("Initializing %s", NAME); + } + log_debugf("MQStart options=%s qmgr=%s", ((Options == MQZIO_SECONDARY) ? "Secondary" : "Primary"), trim(QMgrName)); + + if (!htpass_valid_file(HTPASSWD_FILE)) + { + CC = MQCC_FAILED; + Reason = MQRC_INITIALIZATION_FAILED; + } + + // 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)mqhtpass_term_auth, &CC, &Reason); - + { + hc->MQZEP_Call(hc, MQZID_TERM_AUTHORITY, (PMQFUNC)mqhtpass_terminate, &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; @@ -98,24 +112,7 @@ void MQENTRY MQStart( } /** - * 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. + * Called during the connection of any application. */ static void MQENTRY mqhtpass_authenticate_user( PMQCHAR pQMgrName, @@ -146,17 +143,19 @@ static void MQENTRY mqhtpass_authenticate_user( spuser = malloc(pSecurityParms->CSPUserIdLength + 1); if (!spuser) { - log_errorf("Unable to allocate memory"); + log_errorf("%s is unable to allocate memory for a user", NAME); return; } strncpy(spuser, pSecurityParms->CSPUserIdPtr, pSecurityParms->CSPUserIdLength); spuser[pSecurityParms->CSPUserIdLength] = 0; - sppass = malloc(pSecurityParms->CSPPasswordLength + 1); + sppass = malloc((pSecurityParms->CSPPasswordLength + 1)); if (!sppass) { - log_errorf("Unable to allocate memory"); + log_errorf("%s is unable to allocate memory for a password", NAME); if (spuser) + { free(spuser); + } return; } strncpy(sppass, pSecurityParms->CSPPasswordPtr, pSecurityParms->CSPPasswordLength); @@ -175,61 +174,76 @@ static void MQENTRY mqhtpass_authenticate_user( else { log_debugf( - "Failed to authenticate user=%s effuser=%s applname=%s cspuser=%s cc=%d reason=%d", - pIdentityContext->UserIdentifier, - pApplicationContext->EffectiveUserID, - pApplicationContext->ApplName, - spuser, + "User authentication failed user=%s effuser=%s applname=%s cspuser=%s cc=%d reason=%d", + trim(pIdentityContext->UserIdentifier), + trim(pApplicationContext->EffectiveUserID), + trim(pApplicationContext->ApplName), + trim(spuser), *pCompCode, *pReason); } if (spuser) + { free(spuser); + } if (sppass) + { free(sppass); + } } else { // Password not supplied, so just check that the user ID is valid spuser = malloc(sizeof(PMQCHAR12) + 1); - if (!sppass) + if (!spuser) { - log_errorf("Unable to allocate memory"); + log_errorf("%s is unable to allocate memory to check a user", NAME); return; } 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) + 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) { - *pCompCode = MQCC_OK; + // 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; - memcpy(pIdentityContext->UserIdentifier, spuser, sizeof(pIdentityContext->UserIdentifier)); } else { - log_debugf( - "Invalid user=%s effuser=%s applname=%s cspuser=%s cc=%d reason=%d", - pIdentityContext->UserIdentifier, - pApplicationContext->EffectiveUserID, - pApplicationContext->ApplName, - spuser, - *pCompCode, - *pReason); + bool valid_user = htpass_valid_user(HTPASSWD_FILE, spuser); + if (valid_user) + { + // An OK completion code means MQ will accept this user is authenticated + *pCompCode = MQCC_OK; + *pReason = MQRC_NONE; + *pContinuation = MQZCI_CONTINUE; + 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", + trim(pIdentityContext->UserIdentifier), + trim(pApplicationContext->EffectiveUserID), + trim(pApplicationContext->ApplName), + trim(spuser), + *pCompCode, + *pReason); + } + if (spuser) + { + free(spuser); + } } - if (spuser) - free(spuser); } 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. + * Called during MQDISC, as the inverse of the call to authenticate. */ static void MQENTRY mqhtpass_free_user( PMQCHAR pQMgrName, @@ -247,12 +261,9 @@ static void MQENTRY mqhtpass_free_user( } /** - * 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. + * Called when the authorization service is terminated. */ -static void MQENTRY mqhtpass_term_auth( +static void MQENTRY mqhtpass_terminate( MQHCONFIG hc, MQLONG Options, PMQCHAR pQMgrName, @@ -260,7 +271,7 @@ static void MQENTRY mqhtpass_term_auth( PMQLONG pCompCode, PMQLONG pReason) { - log_debugf("mqhtpass_term_auth()"); + log_infof("Terminating %s", NAME); if (Options == MQZTO_PRIMARY) { log_close(); diff --git a/cmd/runmqserver/logging.go b/cmd/runmqserver/logging.go index 776f4d4..b21a5c5 100644 --- a/cmd/runmqserver/logging.go +++ b/cmd/runmqserver/logging.go @@ -100,6 +100,11 @@ func mirrorQueueManagerErrorLogs(ctx context.Context, wg *sync.WaitGroup, name s return mirrorLog(ctx, wg, f, fromStart, mf, true) } +// mirrorHTPasswdLogs starts a goroutine to mirror the contents of the MQ HTPasswd authorization service's log +func mirrorHTPasswdLogs(ctx context.Context, wg *sync.WaitGroup, name string, fromStart bool, mf mirrorFunc) (chan error, error) { + return mirrorLog(ctx, wg, "/var/mqm/errors/mqhtpass.json", false, mf, true) +} + func getDebug() bool { debug := os.Getenv("DEBUG") if debug == "true" || debug == "1" { @@ -124,7 +129,7 @@ func configureLogger(name string) (mirrorFunc, error) { return false } if err != nil { - log.Printf("Failed to unmarshall JSON - %v", msg) + log.Printf("Failed to unmarshall JSON in log message - %v", msg) } else { fmt.Println(msg) } @@ -142,7 +147,7 @@ func configureLogger(name string) (mirrorFunc, error) { return false } if err != nil { - log.Printf("Failed to unmarshall JSON - %v", err) + log.Printf("Failed to unmarshall JSON in log message - %v", err) } else { fmt.Printf(formatBasic(obj)) // fmt.Printf(formatSimple(obj["ibm_datetime"].(string), obj["message"].(string))) diff --git a/cmd/runmqserver/main.go b/cmd/runmqserver/main.go index 13ea28c..362a229 100644 --- a/cmd/runmqserver/main.go +++ b/cmd/runmqserver/main.go @@ -188,6 +188,13 @@ func doMain() error { logTermination(err) return err } + if *devFlag { + _, err = mirrorHTPasswdLogs(ctx, &wg, name, newQM, mf) + if err != nil { + logTermination(err) + return err + } + } err = updateCommandLevel() if err != nil { logTermination(err) diff --git a/docs/pluggable-connauth.md b/docs/pluggable-connauth.md index 6ea061b..e8a32d4 100644 --- a/docs/pluggable-connauth.md +++ b/docs/pluggable-connauth.md @@ -24,6 +24,6 @@ Use an administrative tool or your application to connect to queue manager using #### Troubleshooting -A log file named `amqpasdev.log` is generated under `/var/mqm/errors` directory path of the container. This file will contain all the failed connection authentication requests. +A log file named `mqhtpass.log` is generated under `/var/mqm/errors` directory path of the container. This file will contain all the failed connection authentication requests. Additional information is logged to this file if the environment variable `DEBUG` is set to `true`. -**Please note**: This log file is based on circular logging and the maximum size is restricted to 1MB. +**Please note**: This log file will be wiped when the queue manager is next started. diff --git a/docs/security.md b/docs/security.md index fa63852..d9a008e 100644 --- a/docs/security.md +++ b/docs/security.md @@ -18,18 +18,3 @@ docker run \ --detach \ ibm-mqadvanced-server:9.2.1.0-amd64 ``` - -The MQ Advanced for Developers image does require the "chown", "setuid", "setgid" and "audit_write" capabilities (plus "dac_override" if you're using an image based on Red Hat Enterprise Linux). This is because it uses the "sudo" command to change passwords inside the container. For example, in Docker, you could do the following: - -```sh -docker run \ - --cap-drop=ALL \ - --cap-add=CHOWN \ - --cap-add=SETUID \ - --cap-add=SETGID \ - --cap-add=AUDIT_WRITE \ - --env LICENSE=accept \ - --env MQ_QMGR_NAME=QM1 \ - --detach \ - ibm-mqadvanced-server-dev:9.2.1.0-amd64 -``` diff --git a/incubating/mqadvanced-server-dev/install-extra-packages.sh b/incubating/mqadvanced-server-dev/install-extra-packages.sh index 51d9ed2..d8518cf 100644 --- a/incubating/mqadvanced-server-dev/install-extra-packages.sh +++ b/incubating/mqadvanced-server-dev/install-extra-packages.sh @@ -36,6 +36,6 @@ if ($YUM); then fi if ($MICRODNF); then - microdnf install apr-util-openssl - microdnf clean all + microdnf --disableplugin=subscription-manager install apr-util-openssl + microdnf --disableplugin=subscription-manager clean all fi \ No newline at end of file diff --git a/install-mq-server-prereqs.sh b/install-mq-server-prereqs.sh index b2385ae..e5d4c99 100644 --- a/install-mq-server-prereqs.sh +++ b/install-mq-server-prereqs.sh @@ -66,7 +66,7 @@ if ($RPM); then EXTRA_RPMS="bash bc ca-certificates file findutils gawk glibc-common grep ncurses-compat-libs passwd procps-ng sed shadow-utils tar util-linux which" # Install additional packages required by MQ, this install process and the runtime scripts $YUM && yum -y install --setopt install_weak_deps=false ${EXTRA_RPMS} - $MICRODNF && microdnf install ${EXTRA_RPMS} + $MICRODNF && microdnf --disableplugin=subscription-manager install ${EXTRA_RPMS} fi # Apply any bug fixes not included in base Ubuntu or MQ image. @@ -78,4 +78,4 @@ $UBUNTU && apt-get install -y libapparmor1 libsystemd0 systemd systemd-sysv libu $UBUNTU && rm -rf /var/lib/apt/lists/* $YUM && yum -y clean all $YUM && rm -rf /var/cache/yum/* -$MICRODNF && microdnf clean all +$MICRODNF && microdnf --disableplugin=subscription-manager clean all From 12a2dee1751487aaca8b918bd2df679bfd5db98e Mon Sep 17 00:00:00 2001 From: Arthur Barr Date: Thu, 10 Dec 2020 16:43:09 +0000 Subject: [PATCH 05/10] Add hostname to log --- authservice/mqhtpass/src/log.c | 4 ++++ authservice/mqhtpass/src/mqhtpass.c | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/authservice/mqhtpass/src/log.c b/authservice/mqhtpass/src/log.c index 773cea6..84a8205 100644 --- a/authservice/mqhtpass/src/log.c +++ b/authservice/mqhtpass/src/log.c @@ -25,6 +25,7 @@ limitations under the License. FILE *fp = NULL; int pid; +char hostname[255]; bool debug = false; /** @@ -49,6 +50,8 @@ 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"); @@ -124,6 +127,7 @@ void log_printf(const char *source_file, int source_line, const char *level, con cur += snprintf(cur, end-cur, ", \"ibm_datetime\":\"%s.%03ldZ\"", date_buf, now.tv_usec / 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\":\""); diff --git a/authservice/mqhtpass/src/mqhtpass.c b/authservice/mqhtpass/src/mqhtpass.c index 0076661..854d1f9 100644 --- a/authservice/mqhtpass/src/mqhtpass.c +++ b/authservice/mqhtpass/src/mqhtpass.c @@ -271,11 +271,14 @@ static void MQENTRY mqhtpass_terminate( PMQLONG pCompCode, PMQLONG pReason) { - log_infof("Terminating %s", NAME); if (Options == MQZTO_PRIMARY) { + log_infof("Terminating %s", NAME); log_close(); } + else { + log_debugf("Terminating secondary"); + } *pCompCode = MQCC_OK; *pReason = MQRC_NONE; } From c0e05be7918b759ad08ed4a7bea560806e654e9d Mon Sep 17 00:00:00 2001 From: Arthur Barr Date: Thu, 10 Dec 2020 16:44:16 +0000 Subject: [PATCH 06/10] Don't use Docker BuildKit --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index f9d230f..136620d 100644 --- a/Makefile +++ b/Makefile @@ -71,6 +71,8 @@ LTS ?= false ############################################################################### # Other variables ############################################################################### +# Build doesn't work if BuildKit is enabled +DOCKER_BUILDKIT=0 GO_PKG_DIRS = ./cmd ./internal ./test MQ_ARCHIVE_TYPE=LINUX MQ_ARCHIVE_DEV_TYPE=Linux From d2ea17ec30ea60263416dd186cc1635a788621f9 Mon Sep 17 00:00:00 2001 From: Arthur Barr Date: Thu, 10 Dec 2020 16:44:43 +0000 Subject: [PATCH 07/10] Use MQ 9.2 client in tests --- test/messaging/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/messaging/pom.xml b/test/messaging/pom.xml index bbdd556..ee9a9be 100644 --- a/test/messaging/pom.xml +++ b/test/messaging/pom.xml @@ -1,5 +1,5 @@