Compare commits

...

143 Commits

Author SHA1 Message Date
LPowlett
dc00231ec1 changelog for v912-UBI (#344)
* changelog for v912-UBI
2019-06-25 10:27:40 +01:00
Rob Parker
8a8ea6c6a9 Merge pull request #336 from mamgainp/windows
Windows
2019-06-13 09:39:10 +01:00
mamgainp
9714d0d513 fixes ifelse ladder 2019-06-12 15:39:41 +01:00
Paras Mamgain
6691438fa1 fixes DOWNLOAD_DIR variable for windows 2019-06-11 15:13:10 +01:00
Paras Mamgain
426c6c3b9a fixes DOWNLOAD_DIR variable for windows 2019-06-11 13:59:38 +01:00
Paras Mamgain
8088cb2902 fixes DOWNLOAD_DIR variable for windows 2019-06-11 13:59:22 +01:00
Rob Parker
a2ce23aa96 Merge pull request #334 from parrobe/capifix
use capicmd for renaming certs
2019-06-10 10:58:24 +01:00
Arthur Barr
95ba16cdb2 Merge branch 'master' into capifix 2019-06-10 10:30:50 +01:00
Rob Parker
83fe77b222 Labels and Licenses for RedHat Certification (#329)
* Add required red hat labels

* Add licenses into correct location for RedHat

* Remove reference to CIP

* PR Comments
2019-06-10 10:29:51 +01:00
Robert Parker
7ab30723cd use capicmd for renaming certs 2019-06-07 16:15:42 +01:00
Stephen Marshall
3f9fc0eaa5 Updates to resolve issues identified by gosec (#330)
* Updates to resolve issues identified by gosec
2019-06-07 11:51:34 +01:00
Arthur Barr
ee4351e55d Remove custom RPM notices file 2019-06-04 15:08:24 +01:00
Rob Parker
ebf55608d7 Merge pull request #324 from parrobe/pwfix
Set password length to meet VA scan requirements
2019-05-31 13:11:40 +01:00
Robert Parker
b3fd5f7562 typos 2019-05-31 11:47:02 +01:00
Robert Parker
3c9ec5f14c Add fix for OIDC error 2019-05-31 11:36:04 +01:00
Robert Parker
81c0b70a6f Set password length to meet VA scan requirements 2019-05-31 10:21:14 +01:00
Robert Parker
a6f307c6b5 Remove openssl 2019-05-30 16:53:27 +01:00
Robert Parker
6f677e2a59 Remove old Integration TLS function 2019-05-30 15:24:48 +01:00
Rob Parker
612fe3a9ec Merge pull request #319 from parrobe/tls
Add TLS Support
2019-05-30 10:40:51 +01:00
Robert Parker
0a9c745d96 PR comments 2019-05-30 10:09:24 +01:00
Robert Parker
b64c060ef4 Refactor TLS code 2019-05-30 09:16:40 +01:00
Robert Parker
8e22763f16 Add TLS Support 2019-05-30 09:16:40 +01:00
Stephen Marshall
40b64e620e Change image tag to amd64 2019-05-30 09:15:45 +01:00
LPowlett
700cc53c07 Handle /var/mqm/ permissions in upgrade to ubi (#316)
* handle /var/mqm/ permissions in upgrade to ubi
2019-05-29 11:40:19 +01:00
Arthur Barr
44d75b169c Remove old icon from README 2019-05-22 16:21:04 +01:00
Rob Parker
c0bf371b9e Merge pull request #314 from parrobe/makefix
Fix makefile targets
2019-05-17 12:53:24 +01:00
Robert Parker
e362644a55 Fix makefile targets 2019-05-17 08:29:32 +01:00
Stephen Marshall
c079c1b60d Merge CIP code 2019-05-16 08:58:02 +01:00
Arthur Barr
4a3bdf3b53 Containerized build 2019-05-10 09:24:36 +01:00
Arthur Barr
5ff269d2e3 Use Universal Base Image 2019-05-10 09:24:36 +01:00
Stephen Marshall
3fb2d3fe61 Fix liveness probe for miqm (#308)
* Fix liveness probe for miqm
2019-05-02 10:16:42 +01:00
Stephen Marshall
6c72c894f7 add multi-instance Queue Managers (#307)
* Initial code to implement multi-instance queue manager

* alter default mqsc to prevent race between listeners on standby startup

* Updates to multi-instance queue manager code

* initial multi instance test

* Multi-instance code improvements

* Multi instance fixes and first test

* configure queue manager

* Add mirror log filtering for mult-instance QMs

* Add log message for multi-instance enabled

* Improvements to container runtime logging

* refactor test

* Test active standby switch

* Improve createQueueManager function

* Test multi instance race

* wait

* multi instance mount tests

* skip race test

* mount tests

* no mount test

* single instance split mount tests

* readiness check

* More updates for handling standby queue manager

* Improve standby checks

* Minor fixes to miqm

* Fix logging of JSON errors

* Update copyrights

* Fix log includes
2019-05-01 14:42:25 +01:00
Rob Parker
63af43f19d Expand redaction system to handle uncommon MQSC cases (#304)
* Expand redaction system to handle uncommon MQSC cases

* add invalidMQSC fixes

* address PR comments
2019-04-17 10:34:29 +01:00
Stephen Marshall
64bb5aed8a Remove debug information 2019-04-12 13:15:02 +01:00
Rob Parker
f05a7d3eaf Merge pull request #300 from sdmarshall79/redact-mqsc-more
Handle multiple-spaces when redacting MQSC output
2019-04-09 15:43:49 +01:00
Stephen Marshall
723fe2b998 Handle multiple-spaces when redacting MQSC output 2019-04-09 15:06:25 +01:00
Stephen Marshall
44d0e0a432 Redact sensitive MQSC logs 2019-04-08 08:49:06 +01:00
LPowlett
33defc0fc9 Handle large mqsc files (#295)
* buffer mqsc input to runmqsc

* imports

* error handling on runmqsc
2019-04-04 16:46:07 +01:00
LPowlett
d69befed71 Version 9.1.2.0 (#294)
* Version 9.1.2.0
2019-03-21 18:25:08 +00:00
Rob Parker
0934289b61 Merge pull request #292 from LPowlett/912-release
912 release manifest update
2019-03-21 16:41:11 +00:00
Luke Powlett
f6231cd51c cert expiry 2019-03-21 16:15:31 +00:00
Luke Powlett
d5f04bc470 copyright notice update 2019-03-21 09:57:59 +00:00
Luke Powlett
d8cbf4566e 9.1.2 manifest update 2019-03-21 09:56:14 +00:00
Rob Parker
493bc7bfd4 Merge pull request #286 from parrobe/gosec
fix gosec failures
2019-03-08 13:36:54 +00:00
Robert Parker
1fa4f6f148 fix gosec failures 2019-03-08 12:59:54 +00:00
Rob Parker
ce664dd654 Merge pull request #285 from parrobe/redistdownload
Fixes for builds
2019-03-08 12:47:11 +00:00
Robert Parker
f8e057a1d6 Fix test breaks from unused code 2019-03-08 11:37:45 +00:00
Robert Parker
efd550822d merge latest from master 2019-03-08 11:13:14 +00:00
Robert Parker
d4df05fd2a Add test fixes and fix mq-explorer build 2019-03-08 11:07:44 +00:00
LPowlett
194b04ac13 Revert MQSC error check (#283)
* Revert MQSC error check
2019-03-08 09:13:26 +00:00
Robert Parker
6848038165 hard code the redist client version 2019-03-06 10:03:38 +00:00
Rob Parker
0d3e177147 Merge pull request #282 from arthurbarr/master
Fix POWER and z/Linux builds
2019-03-06 09:58:15 +00:00
Arthur Barr
13f620f21a Increase test timeouts 2019-03-05 15:08:07 +00:00
Arthur Barr
d4a81741cc Prevent re-download of image during build 2019-03-05 15:07:51 +00:00
Arthur Barr
0047301335 Use Makefile target build-sdk-ex 2019-03-05 11:31:27 +00:00
Arthur Barr
5ef532d2c1 Don't use redist client for Go SDK because of P+Z support 2019-03-05 10:19:21 +00:00
Arthur Barr
281cdc4578 Update RHEL build README 2019-03-05 09:47:58 +00:00
Rob Parker
d68c051104 Merge pull request #280 from parrobe/rhelpackages
Add in system to generate licenses on RHEL
2019-03-04 16:00:49 +00:00
Rob Parker
c5a52e616c Merge branch 'master' into rhelpackages 2019-02-27 15:35:11 +00:00
Arthur Barr
c441de7d26 Fixes for tests on RHEL and test log output 2019-02-27 15:32:34 +00:00
Arthur Barr
a194545f08 Don't add root user into mqm group 2019-02-27 15:32:34 +00:00
Arthur Barr
4f57d1bae2 Tidy up commented code 2019-02-27 15:32:34 +00:00
Arthur Barr
cc0f072908 Change for running as a non-root user (#276)
* Enable running container as mqm

* Fix merge problem

* Don't force root usage

* RHEL image runs as mqm instead of root

* Build on host with SELinux enabled

* Enable building on node in an OpenShift cluster

* Enable running container as mqm

* Fix merge problem

* Don't force root usage

* Merge lastest changes from master

* RHEL image runs as mqm instead of root

* Fix merge issues

* Test changes for non-root

* Make timeout properly, and more non-root test fixes

* Run tests with fewer/no capabilities

* Correct usage docs for non-root

* Add security docs

* Add temporary debug output

* Remove debug code

* Fixes for termination-log

* Allow init container to run as root

* Fixes for CentOS build

* Fixes for RHEL build

* Logging improvements

* Fix Dockerfile RHEL/CentOS build

* Fix bash error

* Make all builds specify UID

* Use redist client for Go SDK

* Inspect image before running tests

* New test for init container

* Log container runtime in runmqdevserver

* Add extra capabilities if using a RHEL image
2019-02-27 15:32:34 +00:00
Robert Parker
d834ac7c9c Add in system to generate licenses on RHEL 2019-02-27 15:06:09 +00:00
Rob Parker
2dbee560fe Merge pull request #272 from parrobe/master
fix dockerfile reference to build explorer
2019-02-12 11:33:20 +00:00
Arthur Barr
17d3238161 Merge branch 'master' into master 2019-02-07 17:07:45 +00:00
Luke Powlett
c08ca2e79f update travis to use Ubuntu 16.04 2019-02-07 16:47:32 +00:00
Rob Parker
84df0e8362 Merge branch 'master' into master 2019-02-07 11:41:51 +00:00
Rob Parker
cc213f429f Merge pull request #273 from arthurbarr/make
Fixes for RHEL build
2019-02-07 11:41:34 +00:00
Rob Parker
c29159dd38 Merge branch 'master' into make 2019-02-07 10:49:02 +00:00
Rob Parker
f345ccf920 Merge branch 'master' into master 2019-02-07 10:48:51 +00:00
Rob Parker
d1b1cfc5d8 Merge pull request #274 from parrobe/golint
Use documented way to install golint
2019-02-07 10:48:39 +00:00
Robert Parker
a19c455ea4 up the copyright date 2019-02-07 10:29:41 +00:00
Robert Parker
694b31d6e8 Use documented way to install golint 2019-02-07 10:09:53 +00:00
Arthur Barr
33f82d76ff Use variable for uid and gid 2019-02-06 16:12:18 +00:00
Arthur Barr
77319629fe Update copyright dates 2019-02-06 16:11:52 +00:00
Arthur Barr
d9c70c48c5 Fix subscription checking logic 2019-02-06 14:23:25 +00:00
Arthur Barr
599f5f4b53 Build on host with SELinux enabled 2019-02-06 13:39:42 +00:00
Arthur Barr
6840a575f9 Fix for building on subscribed and unsubscribed hosts 2019-02-06 13:29:44 +00:00
Robert Parker
1b8c816f57 fix dockerfile reference to build explorer 2019-02-05 15:54:31 +00:00
Arthur Barr
9a8ff9b524 copy sub-directories under /etc/yum.repos.d 2019-02-05 13:53:59 +00:00
Arthur Barr
f3c858184f use correct download files in RHEL build process 2019-02-05 13:51:15 +00:00
LPowlett
43676049b7 RHEL Security Vulnerability Test (#270)
* check for security vulnerabilities on rhel

* import

* check host is red hat

* filepath join

* imports
2019-02-04 10:25:55 +00:00
LPowlett
df6ce917c2 endmqm -r try to reconnect (#268) 2019-01-28 11:03:54 +00:00
Rob Parker
d3eb6e0d3d Merge pull request #267 from arthurbarr/master
RHEL build uses redist client and RHEL minimal
2019-01-25 13:49:46 +00:00
Arthur Barr
2bfdd51a01 RHEL build uses redist client and RHEL minimal 2019-01-24 13:51:10 +00:00
LPowlett
be11b3cda1 Dependancy upgrades (#264)
* dependancy upgrades

* revert dep to 0.4.1

* revert file changes
2019-01-10 14:55:22 +00:00
LPowlett
525ff82fe7 Error checking invalid mqsc commands (#261)
* testRepeatingMqsc

* testInvalidMqsc

* 9.1.1 Makefile

* testRepeatingMqsc

* testInvalidMqsc

* update copyright 2019

* update invalid mqsc error message

* update changelog to reflect MQSC changes
2019-01-10 13:41:43 +00:00
Rob Parker
3e07814bf6 update travis build stages to remove duplicate build and label (#263)
* update travis build stages to remove duplicate build and label

* update copyright to 2019
2019-01-10 10:21:20 +00:00
Rob Parker
b1daacf377 Merge pull request #256 from parrobe/releasetag
Add image tag into info output, add ability to print info output on d…
2018-12-12 13:51:26 +00:00
Robert Parker
9c8b3825be Add image tag into info output, add ability to print info output on demand and prevent multiple instances of runmqserver being run 2018-12-12 13:11:47 +00:00
Rob Parker
4145f077b6 update perl-base to fix security vulnerability (#253) 2018-12-05 13:01:31 +00:00
Robert Parker
c063ddd67d remove deprecated tag manifest 2018-12-03 11:31:24 +00:00
Rob Parker
5f000ff891 Update to IBM MQ version 9.1.1.0 (#251)
* Update to MQ v9.1.1

* update incubating to 911

* check docker version on travis

* travis docker version is now high enough
2018-11-30 17:26:19 +00:00
Luke Powlett
e33710eb00 remove create qm option from console 2018-11-09 17:06:36 +00:00
LPowlett
2fdd2c51ff Merge pull request #244 from parrobe/master
Security fixes, Nov 2018
2018-11-07 13:21:26 +00:00
Robert Parker
b73ad12011 Security fixes, Nov 2018 2018-11-07 11:36:04 +00:00
LPowlett
b276e0b4ef move image builds to before_script (#237) 2018-10-24 09:19:39 +01:00
Robert Parker
a3c0af9648 Add sudo to groupadd 2018-10-18 16:15:07 +01:00
Rob Parker
1208a5d08b add RHEL into the docker tag for RHEL builds (#234) 2018-10-18 13:50:20 +01:00
Robert Parker
fe8a87b39f Fix build failure in RHEL makefile 2018-10-18 11:54:26 +01:00
Arthur Barr
9a34e9b15c Clarify docs and fix links (#226) 2018-10-12 11:26:24 +01:00
Rob Parker
78ce84b3a1 Implement GOSec for security scanning Fix vulnerabilities (#227)
* Implement GOSec for security scanning Fix vulnerabilities

* Fix lint failure

* address PR comments and fix build break

* Fix test break in mqsc
2018-10-11 15:39:22 +01:00
Robert Parker
6d11b0d8ae update apparmor 2018-10-09 10:00:30 +01:00
Arthur Barr
d0fce28ef3 build Ubuntu image on non-RHEL hosts 2018-10-02 13:59:39 +01:00
Arthur Barr
31f604cc47 Document build prereqs for RHEL 2018-10-01 16:20:08 +01:00
Arthur Barr
c6a921efee Tidy up Travis output (#213)
* Ignore generated Prometheus files

* Tidy up Travis output

* Use relative path for script

* Futher tidying of Travis output
2018-10-01 10:14:35 +01:00
Stephen Marshall
822a073c4e Fix RTC ppcle_linux build (#211) 2018-09-21 09:30:53 +01:00
Rob Parker
a854c4c627 explicitly set root user for RHEL containers (#208) 2018-09-12 16:32:39 +01:00
Rob Parker
3989661778 Add Diagnostics (#203)
* Add container suplimentary groups support

* Add diagnostic gathering

* Fix incorrect userid group searching

* one last tiny fix

* one last tiny fix
2018-09-03 17:08:26 +01:00
Robert Parker
9a7d44fef6 Add container suplimentary groups support 2018-09-03 15:01:26 +01:00
Rob Parker
f73347a9cf Merge pull request #201 from parrobe/master
Further RHEL fixes
2018-08-30 09:13:33 +01:00
Robert Parker
ad1b2db1fd Further RHEL fixes 2018-08-29 11:25:44 +01:00
Stephen Marshall
11b94de1c0 Merge pull request #199 from parrobe/master
Various fixes for the RHEL image and tests
2018-08-24 10:07:04 +01:00
Robert Parker
1906896038 Fix TestDevSecure on RHEL 2018-08-24 09:32:41 +01:00
Robert Parker
d5ec1fa505 correct the useradd command 2018-08-23 10:26:28 +01:00
Robert Parker
e03ceff7c9 Fix dev image uid and build off rhel7 2018-08-23 10:16:14 +01:00
Robert Parker
64f5ce3624 specify tag for JMS image 2018-08-21 15:56:56 +01:00
Robert Parker
b921bb82d3 Create RHEL test make targets 2018-08-21 15:40:32 +01:00
Robert Parker
92e57f2cb1 correctly set up volume directory 2018-08-21 14:33:00 +01:00
Robert Parker
0281e52b47 Test fixes for RHEL image 2018-08-21 11:49:56 +01:00
Rob Parker
8bdba41f75 Merge pull request #193 from parrobe/buildah
Restructure makefile to add common targets and auto-detect which target to call when using defaults
2018-08-21 08:53:30 +01:00
Robert Parker
aa04229d85 Call correct make target when using default make targets 2018-08-20 15:50:48 +01:00
Robert Parker
9e04bfc68a Create MQ RHEL buildah build 2018-08-20 15:50:48 +01:00
Robert Parker
8fcdfeb1c4 Move and rename files for RHEL build 2018-08-20 15:50:48 +01:00
Arthur Barr
a2d3abfb86 Minor fixes to buildah build 2018-08-20 15:50:09 +01:00
Arthur Barr
e66bcfd77f Buildah containerized Go build 2018-08-20 15:48:42 +01:00
Rob Parker
d2ea8d4f06 Create IBM MQ RHEL Buildah build (#192)
* Buildah containerized Go build

* Minor fixes to buildah build

* Move and rename files for RHEL build

* Create MQ RHEL buildah build
2018-08-20 13:49:10 +01:00
Stephen Marshall
fc19776c04 Fix lint error 2018-08-13 12:31:47 +01:00
Arthur Barr
3f07b1e77f Fix logo in README 2018-08-07 15:51:02 +01:00
Arthur Barr
00568adc6d Remove unused variable in Dockerfiles 2018-08-07 15:15:45 +01:00
Arthur Barr
ba1b4f8ef9 Remove debug statements 2018-08-07 15:15:45 +01:00
Arthur Barr
9b98555886 Enable only app user to do REST messaging 2018-08-07 15:15:45 +01:00
Arthur Barr
dbfc47591e Fix Mac incompatibility of image creation date 2018-08-07 15:15:45 +01:00
Arthur Barr
b087f37505 Add master branch warning to README 2018-08-07 15:15:45 +01:00
Arthur Barr
08299dd0d1 Enable admin and app users to do REST messaging 2018-08-07 15:15:45 +01:00
Rob Parker
b8f96d0148 Merge pull request #181 from parrobe/master
make sure to use -aG not -G
2018-08-06 10:46:04 +01:00
Robert Parker
6bb669d6ac make sure to use -aG not -G 2018-08-06 10:16:23 +01:00
Rob Parker
80384a65a2 force string output in chkmqhealthy (#174) 2018-08-02 13:53:22 +01:00
Rob Parker
c11c0a2bf4 check explicitly for /mnt/mqm (#175) 2018-08-02 11:42:14 +01:00
Rob Parker
29dfe38d32 Merge pull request #176 from parrobe/dlfix
only download the sdk image if we need to
2018-07-27 13:31:15 +01:00
Robert Parker
89eae35724 only download the sdk image if we need to 2018-07-27 12:23:08 +01:00
Robert Parker
b751640b79 Update license to 9.1 2018-07-25 12:37:09 +01:00
130 changed files with 7497 additions and 1449 deletions

5
.gitignore vendored
View File

@@ -7,3 +7,8 @@ build
coverage coverage
downloads downloads
incubating/mqipt/ms81* incubating/mqipt/ms81*
vendor/github.com/prometheus/client_model/bin/
vendor/github.com/prometheus/client_model/.classpath
vendor/github.com/prometheus/client_model/.project
vendor/github.com/prometheus/client_model/.settings*
gosec_results.json

View File

@@ -1,4 +1,4 @@
# © Copyright IBM Corporation 2018 # © Copyright IBM Corporation 2018, 2019
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
dist: xenial
sudo: required sudo: required
language: go language: go
@@ -27,43 +29,52 @@ cache:
directories: directories:
- downloads - downloads
env:
- BASE_IMAGE=ubuntu:16.04
# Commented out temporarily until Issue 166 is resolved
# - BASE_IMAGE=centos:latest
jobs: jobs:
include: include:
- if: type IN (pull_request) - stage: build and test
env: DOCKER_DOWNGRADE="docker save -o images.tar mqadvanced-server-dev mq-dev-jms-test && env:
sudo apt-get autoremove -y docker-ce && - BASE_IMAGE=ubuntu:16.04
curl -fsSL \"https://apt.dockerproject.org/gpg\" | sudo apt-key add - && - DOCKER_DOWNGRADE="echo nothing to be done"
sudo apt-add-repository \"deb https://apt.dockerproject.org/repo ubuntu-$(lsb_release -cs) main\" && - env:
sudo apt-get update && - BASE_IMAGE=centos:7
sudo apt-get install docker-engine=1.12.6-0~ubuntu-$(lsb_release -cs) && - DOCKER_DOWNGRADE="echo nothing to be done"
docker load -q -i images.tar && - if: type IN (pull_request) OR tag IS present
export DOCKER_API_VERSION=\"1.24\"" env:
- env: DOCKER_DOWNGRADE="echo nothing to be done" - BASE_IMAGE=ubuntu:16.04
- DOCKER_DOWNGRADE="docker save -o images.tar mqadvanced-server-dev mq-dev-jms-test &&
sudo apt-get autoremove -y docker-ce &&
curl -fsSL \"https://apt.dockerproject.org/gpg\" | sudo apt-key add - &&
sudo apt-add-repository \"deb https://apt.dockerproject.org/repo ubuntu-$(lsb_release -cs) main\" &&
sudo apt-get update &&
sudo apt-get install docker-engine=1.12.6-0~ubuntu-$(lsb_release -cs) &&
docker load -q -i images.tar &&
export DOCKER_API_VERSION=\"1.24\""
before_install: before_install:
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - - ./install-build-deps-ubuntu.sh
- sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
- sudo apt-get update
- sudo apt-get -y install docker-ce
- curl https://glide.sh/get | sh
- sudo curl -Lo /usr/local/bin/dep https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64
- sudo chmod +x /usr/local/bin/dep
install: install:
- echo nothing - echo nothing
script: before_script:
- echo 'Downloading Go dependencies...' && echo -en 'travis_fold:start:deps\\r'
- make deps - make deps
- make build-devserver - echo -en 'travis_fold:end:deps\\r'
- echo 'Building Developer image...' && echo -en 'travis_fold:start:build-devserver\\r'
# Use the containerized build explicitly
- make build-devserver-ctr
- echo -en 'travis_fold:end:build-devserver\\r'
- echo 'Building Developer JMS test image...' && echo -en 'travis_fold:start:build-devjmstest\\r'
- make build-devjmstest - make build-devjmstest
- echo -en 'travis_fold:end:build-devjmstest\\r'
script:
- echo 'Downgrading Docker (if necessary)...' && echo -en 'travis_fold:start:docker-downgrade\\r'
- eval "$DOCKER_DOWNGRADE" - eval "$DOCKER_DOWNGRADE"
- echo -en 'travis_fold:end:docker-downgrade\\r'
- echo 'Testing Developer image...' && echo -en 'travis_fold:start:test-devserver\\r'
- make test-devserver - make test-devserver
- echo -en 'travis_fold:end:test-devserver\\r'
after_success: after_success:
- go get golang.org/x/lint/golint
- make lint - make lint

View File

@@ -1,5 +1,44 @@
# Change log # Change log
## 9.1.2.0-UBI (2019-06-21)
**Breaking changes**:
* UID of the mqm user is now 888. You need to run the container with an entrypoint of `runmqserver -i` under the root user to update any existing files.
* MQSC files supplied will be verified before being run. Files containing invalid MQSC will cause the container to fail to start
**Other changes**:
* Security fixes
* Web console added to production image
* Container built on RedHat host
## 9.1.2.0 (2019-03-21)
* Now runs using the "mqm" user instead of root. See new [security doc](https://github.com/ibm-messaging/mq-container/blob/master/docs/security.md)
* New [IGNSTATE](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.pro.doc/q132310_.htm#q132310___ignstateparm) parameter used in default developer config
* Termination log moved from `/dev/termination-log` to `/run/termination-log`, to make permissions easier to handle
* Fixes for the following issues:
* Brackets no longer appear in termination log
* Test timeouts weren't being used correctly
* Building on subscribed and unsubscribed hosts ([#273](https://github.com/ibm-messaging/mq-container/pull/273))
* Gosec failures ([#286](https://github.com/ibm-messaging/mq-container/pull/286))
* Security fix for perl-base ([#253](https://github.com/ibm-messaging/mq-container/pull/253))
## 9.1.1.0 (2018-11-30)
* Updated to MQ version 9.1.1.0
* Created seperate RedHat Makefile for building images on RedHat machines with buildah
* Enabled REST messaging capability for app user.
* Added support for container supplementary groups
* Removed IBM MQ version 9.0.5 details.
* Added additional Diagnostics ([#203](https://github.com/ibm-messaging/mq-container/pull/203))
* Implementted GOSec to perform code scans for security vulnerabilities. (([#227](https://github.com/ibm-messaging/mq-container/pull/227)))
* Removed Queue manager create option from the MQ Console.
* Fixes for the following issues:
* Check explicitly for `/mnt/mqm` ([#175](https://github.com/ibm-messaging/mq-container/pull/175))
* Force string output in chkmqhealthy ([#174](https://github.com/ibm-messaging/mq-container/pull/174))
* Use -aG not -G when adding a group for a user
* Security fixes for libsystemd0 systemd systemd-sysv & libudev1
## 9.1.0.0 (2018-07-23) ## 9.1.0.0 (2018-07-23)
* Updated to MQ version 9.1.0.0 * Updated to MQ version 9.1.0.0

View File

@@ -1,4 +1,4 @@
# © Copyright IBM Corporation 2015, 2018 # © Copyright IBM Corporation 2015, 2019
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -12,24 +12,35 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
ARG BASE_IMAGE=ubuntu:16.04
ARG BUILDER_IMAGE=mq-golang-sdk:9.0.5.0-x86_64-ubuntu-16.04
############################################################################### ###############################################################################
# Build stage to build Go code # Build stage to build Go code
############################################################################### ###############################################################################
FROM $BUILDER_IMAGE as builder FROM registry.access.redhat.com/devtools/go-toolset-7-rhel7 as builder
WORKDIR /go/src/github.com/ibm-messaging/mq-container/ # FROM docker.io/centos/go-toolset-7-centos7 as builder
# The URL to download the MQ installer from in tar.gz format
# This assumes an archive containing the MQ RPM install packages
ARG MQ_URL="https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev912_linux_x86-64.tar.gz"
ARG IMAGE_REVISION="Not specified" ARG IMAGE_REVISION="Not specified"
ARG IMAGE_CREATED="Not specified"
ARG IMAGE_SOURCE="Not specified" ARG IMAGE_SOURCE="Not specified"
ARG IMAGE_TAG="Not specified"
ARG MQM_UID=888
USER 0
COPY install-mq.sh /usr/local/bin/
RUN chmod a+x /usr/local/bin/install-mq.sh \
&& sleep 1 \
&& MQ_PACKAGES="MQSeriesRuntime-*.rpm MQSeriesSDK-*.rpm MQSeriesSamples*.rpm" install-mq.sh $MQM_UID
WORKDIR /opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/
COPY cmd/ ./cmd COPY cmd/ ./cmd
COPY internal/ ./internal COPY internal/ ./internal
COPY vendor/ ./vendor COPY vendor/ ./vendor
RUN go build -ldflags "-X \"main.ImageCreated=$IMAGE_CREATED\" -X \"main.ImageRevision=$IMAGE_REVISION\" -X \"main.ImageSource=$IMAGE_SOURCE\"" ./cmd/runmqserver/ ENV PATH="${PATH}:/opt/rh/go-toolset-7/root/usr/bin" \
CGO_CFLAGS="-I/opt/mqm/inc/" \
CGO_LDFLAGS_ALLOW="-Wl,-rpath.*"
RUN go build -ldflags "-X \"main.ImageCreated=$(date --iso-8601=seconds)\" -X \"main.ImageRevision=$IMAGE_REVISION\" -X \"main.ImageSource=$IMAGE_SOURCE\" -X \"main.ImageTag=$IMAGE_TAG\"" ./cmd/runmqserver/
RUN go build ./cmd/chkmqready/ RUN go build ./cmd/chkmqready/
RUN go build ./cmd/chkmqhealthy/ RUN go build ./cmd/chkmqhealthy/
# Run all unit tests RUN go build ./cmd/runmqdevserver/
RUN go test -v ./cmd/runmqdevserver/...
RUN go test -v ./cmd/runmqserver/ RUN go test -v ./cmd/runmqserver/
RUN go test -v ./cmd/chkmqready/ RUN go test -v ./cmd/chkmqready/
RUN go test -v ./cmd/chkmqhealthy/ RUN go test -v ./cmd/chkmqhealthy/
@@ -39,37 +50,93 @@ RUN go vet ./cmd/... ./internal/...
############################################################################### ###############################################################################
# Main build stage, to build MQ image # Main build stage, to build MQ image
############################################################################### ###############################################################################
FROM $BASE_IMAGE FROM registry.access.redhat.com/ubi7/ubi-minimal AS mq-server
# The URL to download the MQ installer from in tar.gz format
# This assumes an archive containing the MQ Debian (.deb) install packages
ARG MQ_URL
# The MQ packages to install - see install-mq.sh for default value # The MQ packages to install - see install-mq.sh for default value
ARG MQ_PACKAGES ARG MQ_URL="https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev912_linux_x86-64.tar.gz"
ARG MQ_PACKAGES="MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesWeb*.rpm MQSeriesAMS-*.rpm"
#ARG MQ_PACKAGES="ibmmq-server ibmmq-java ibmmq-jre ibmmq-gskit ibmmq-msg-.* ibmmq-samples ibmmq-web ibmmq-ams"
ARG MQM_UID=888
LABEL summary="IBM MQ Advanced Server"
LABEL description="Simplify, accelerate and facilitate the reliable exchange of data with a security-rich messaging solution — trusted by the worlds most successful enterprises"
LABEL vendor="IBM"
LABEL distribution-scope="private"
LABEL authoritative-source-url="https://www.ibm.com/software/passportadvantage/"
LABEL url="https://www.ibm.com/products/mq/advanced"
LABEL io.openshift.tags="mq messaging"
LABEL io.k8s.display-name="IBM MQ Advanced Server"
LABEL io.k8s.description="Simplify, accelerate and facilitate the reliable exchange of data with a security-rich messaging solution — trusted by the worlds most successful enterprises"
COPY install-mq.sh /usr/local/bin/ COPY install-mq.sh /usr/local/bin/
COPY install-mq-server-prereqs.sh /usr/local/bin/
# Install MQ. To avoid a "text file busy" error here, we sleep before installing. # Install MQ. To avoid a "text file busy" error here, we sleep before installing.
RUN chmod u+x /usr/local/bin/install-mq.sh \ RUN env && chmod u+x /usr/local/bin/install-*.sh \
&& sleep 1 \ && sleep 1 \
&& install-mq.sh && install-mq-server-prereqs.sh $MQM_UID \
&& install-mq.sh $MQM_UID
# Create a directory for runtime data from runmqserver # Create a directory for runtime data from runmqserver
RUN mkdir -p /run/runmqserver \ RUN mkdir -p /run/runmqserver \
&& chown mqm:mqm /run/runmqserver && chown mqm:mqm /run/runmqserver
COPY --from=builder /opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/runmqserver /usr/local/bin/
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqserver /usr/local/bin/ COPY --from=builder /opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/chkmq* /usr/local/bin/
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/chkmq* /usr/local/bin/
COPY NOTICES.txt /opt/mqm/licenses/notices-container.txt COPY NOTICES.txt /opt/mqm/licenses/notices-container.txt
# Copy web XML files
COPY web /etc/mqm/web
COPY etc/mqm/*.tpl /etc/mqm/
RUN chmod ug+x /usr/local/bin/runmqserver \ RUN chmod ug+x /usr/local/bin/runmqserver \
&& chown mqm:mqm /usr/local/bin/*mq* \ && chown mqm:mqm /usr/local/bin/*mq* \
&& chmod ug+xs /usr/local/bin/chkmq* && chmod ug+xs /usr/local/bin/chkmq* \
&& chown -R mqm:mqm /etc/mqm/* \
&& install --directory --mode 0775 --owner mqm --group root /run/runmqserver \
&& touch /run/termination-log \
&& chown mqm:root /run/termination-log \
&& chmod 0660 /run/termination-log
# Always use port 1414 for MQ & 9157 for the metrics # Always use port 1414 for MQ & 9157 for the metrics
EXPOSE 1414 9157 EXPOSE 1414 9157 9443
ENV LANG=en_US.UTF-8 AMQ_DIAGNOSTIC_MSG_SEVERITY=1 AMQ_ADDITIONAL_JSON_LOG=1 LOG_FORMAT=basic ENV LANG=en_US.UTF-8 AMQ_DIAGNOSTIC_MSG_SEVERITY=1 AMQ_ADDITIONAL_JSON_LOG=1 LOG_FORMAT=basic
USER $MQM_UID
ENTRYPOINT ["runmqserver"] ENTRYPOINT ["runmqserver"]
###############################################################################
# Add default developer config
###############################################################################
FROM mq-server AS mq-dev-server
ARG MQM_UID=888
# Enable MQ developer default configuration
ENV MQ_DEV=true
# Default administrator password
ENV MQ_ADMIN_PASSWORD=passw0rd
LABEL summary="IBM MQ Advanced for Developers Server"
LABEL description="Simplify, accelerate and facilitate the reliable exchange of data with a security-rich messaging solution — trusted by the worlds most successful enterprises"
LABEL vendor="IBM"
LABEL distribution-scope="private"
LABEL authoritative-source-url="https://www.ibm.com/software/passportadvantage/"
LABEL url="https://www.ibm.com/products/mq/advanced"
LABEL io.openshift.tags="mq messaging"
LABEL io.k8s.display-name="IBM MQ Advanced for Developers Server"
LABEL io.k8s.description="Simplify, accelerate and facilitate the reliable exchange of data with a security-rich messaging solution — trusted by the worlds most successful enterprises"
USER 0
COPY incubating/mqadvanced-server-dev/install-extra-packages.sh /usr/local/bin/
RUN chmod u+x /usr/local/bin/install-extra-packages.sh \
&& sleep 1 \
&& install-extra-packages.sh
# WARNING: This is what allows the mqm user to change the password of any other user
# It's used by runmqdevserver to change the admin/app passwords.
RUN echo "mqm ALL = NOPASSWD: /usr/sbin/chpasswd" > /etc/sudoers.d/mq-dev-config
## Add admin and app users, and set a default password for admin
RUN useradd admin -G mqm \
&& groupadd mqclient \
&& useradd app -G mqclient \
&& echo admin:$MQ_ADMIN_PASSWORD | chpasswd
# Create a directory for runtime data from runmqserver
RUN mkdir -p /run/runmqdevserver \
&& chown mqm:mqm /run/runmqdevserver
COPY --from=builder /opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/runmqdevserver /usr/local/bin/
# Copy template files
COPY incubating/mqadvanced-server-dev/*.tpl /etc/mqm/
# Copy web XML files for default developer configuration
COPY incubating/mqadvanced-server-dev/web /etc/mqm/web
RUN chown -R mqm:mqm /etc/mqm/* \
&& chmod +x /usr/local/bin/runmq* \
&& install --directory --mode 0775 --owner mqm --group root /run/runmqdevserver
ENV MQ_ENABLE_EMBEDDED_WEB_SERVER=1
USER $MQM_UID
ENTRYPOINT ["runmqdevserver"]

View File

@@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
© Copyright IBM Corporation. 2015, 2018 © Copyright IBM Corporation. 2015, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

285
Makefile
View File

@@ -1,4 +1,4 @@
# © Copyright IBM Corporation 2017, 2018 # © Copyright IBM Corporation 2017, 2019
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -16,14 +16,14 @@
# Conditional variables - you can override the values of these variables from # Conditional variables - you can override the values of these variables from
# the command line # the command line
############################################################################### ###############################################################################
# BASE_IMAGE is the base image to use for MQ, for example "ubuntu" or "rhel"
BASE_IMAGE ?= ubuntu:16.04
# MQ_VERSION is the fully qualified MQ version number to build # MQ_VERSION is the fully qualified MQ version number to build
MQ_VERSION ?= 9.1.0.0 MQ_VERSION ?= 9.1.2.0
# RELEASE shows what release of the container code has been built
RELEASE ?= 3
# MQ_ARCHIVE is the name of the file, under the downloads directory, from which MQ Advanced can # MQ_ARCHIVE is the name of the file, under the downloads directory, from which MQ Advanced can
# be installed. The default value is derived from MQ_VERSION, BASE_IMAGE and architecture # be installed. The default value is derived from MQ_VERSION, BASE_IMAGE and architecture
# Does not apply to MQ Advanced for Developers. # Does not apply to MQ Advanced for Developers.
MQ_ARCHIVE ?= IBM_MQ_$(MQ_VERSION)_$(MQ_ARCHIVE_TYPE)_$(MQ_ARCHIVE_ARCH).tar.gz MQ_ARCHIVE ?= IBM_MQ_$(MQ_VERSION_VRM)_$(MQ_ARCHIVE_TYPE)_$(MQ_ARCHIVE_ARCH).tar.gz
# MQ_ARCHIVE_DEV is the name of the file, under the downloads directory, from which MQ Advanced # MQ_ARCHIVE_DEV is the name of the file, under the downloads directory, from which MQ Advanced
# for Developers can be installed # for Developers can be installed
MQ_ARCHIVE_DEV ?= $(MQ_ARCHIVE_DEV_$(MQ_VERSION)) MQ_ARCHIVE_DEV ?= $(MQ_ARCHIVE_DEV_$(MQ_VERSION))
@@ -31,56 +31,64 @@ MQ_ARCHIVE_DEV ?= $(MQ_ARCHIVE_DEV_$(MQ_VERSION))
MQ_SDK_ARCHIVE ?= $(MQ_ARCHIVE_DEV_$(MQ_VERSION)) MQ_SDK_ARCHIVE ?= $(MQ_ARCHIVE_DEV_$(MQ_VERSION))
# Options to `go test` for the Docker tests # Options to `go test` for the Docker tests
TEST_OPTS_DOCKER ?= TEST_OPTS_DOCKER ?=
# MQ_IMAGE_ADVANCEDSERVER is the name and tag of the built MQ Advanced image # MQ_IMAGE_ADVANCEDSERVER is the name of the built MQ Advanced image
MQ_IMAGE_ADVANCEDSERVER ?=mqadvanced-server:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG) MQ_IMAGE_ADVANCEDSERVER ?=mqadvanced-server
# MQ_IMAGE_DEVSERVER is the name and tag of the built MQ Advanced for Developers image # MQ_IMAGE_DEVSERVER is the name of the built MQ Advanced for Developers image
MQ_IMAGE_DEVSERVER ?=mqadvanced-server-dev:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG) MQ_IMAGE_DEVSERVER ?=mqadvanced-server-dev
# MQ_IMAGE_SDK is the name and tag of the built MQ Advanced for Developers SDK image # MQ_TAG is the tag of the built MQ Advanced image & MQ Advanced for Developers image
MQ_IMAGE_SDK ?=mq-sdk:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG) MQ_TAG ?=$(MQ_VERSION)-$(ARCH)
# MQ_IMAGE_GOLANG_SDK is the name and tag of the built MQ Advanced for Developers SDK image, plus Go tools # DOCKER is the Docker command to run. Defaults to "podman" if it's available, otherwise "docker"
MQ_IMAGE_GOLANG_SDK ?=mq-golang-sdk:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG) DOCKER ?= $(shell type -p podman || echo docker)
# DOCKER is the Docker command to run
DOCKER ?= docker
# MQ_PACKAGES specifies the MQ packages (.deb or .rpm) to install. Defaults vary on base image. # MQ_PACKAGES specifies the MQ packages (.deb or .rpm) to install. Defaults vary on base image.
MQ_PACKAGES ?= MQ_PACKAGES ?=MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesWeb*.rpm MQSeriesAMS-*.rpm
# MQM_UID is the UID to use for the "mqm" user
MQM_UID ?= 888
############################################################################### ###############################################################################
# Other variables # Other variables
############################################################################### ###############################################################################
# ARCH is the platform architecture (e.g. x86_64, ppc64le or s390x) GO_PKG_DIRS = ./cmd ./internal ./test
ARCH = $(shell uname -m) MQ_ARCHIVE_TYPE=LINUX
MQ_ARCHIVE_DEV_PLATFORM=linux
# ARCH is the platform architecture (e.g. amd64, ppc64le or s390x)
ARCH=$(if $(findstring x86_64,$(shell uname -m)),amd64,$(shell uname -m))
# BUILD_SERVER_CONTAINER is the name of the web server container used at build time # BUILD_SERVER_CONTAINER is the name of the web server container used at build time
BUILD_SERVER_CONTAINER=build-server BUILD_SERVER_CONTAINER=build-server
# NUM_CPU is the number of CPUs available to Docker. Used to control how many # NUM_CPU is the number of CPUs available to Docker. Used to control how many
# test run in parallel # test run in parallel
NUM_CPU = $(or $(shell docker info --format "{{ .NCPU }}"),2) NUM_CPU = $(or $(shell docker info --format "{{ .NCPU }}"),2)
# BASE_IMAGE_TAG is a normalized version of BASE_IMAGE, suitable for use in a Docker tag # BASE_IMAGE_TAG is a normalized version of BASE_IMAGE, suitable for use in a Docker tag
BASE_IMAGE_TAG=$(subst /,-,$(subst :,-,$(BASE_IMAGE))) BASE_IMAGE_TAG=$(lastword $(subst /, ,$(subst :,-,$(BASE_IMAGE))))
MQ_IMAGE_DEVSERVER_BASE=mqadvanced-server-dev-base:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG) #BASE_IMAGE_TAG=$(subst /,-,$(subst :,-,$(BASE_IMAGE)))
MQ_IMAGE_DEVSERVER_BASE=mqadvanced-server-dev-base
# Docker image name to use for JMS tests # Docker image name to use for JMS tests
DEV_JMS_IMAGE=mq-dev-jms-test DEV_JMS_IMAGE=mq-dev-jms-test
# Variables for versioning # Variables for versioning
IMAGE_REVISION=$(shell git rev-parse HEAD) IMAGE_REVISION=$(shell git rev-parse HEAD)
IMAGE_SOURCE=$(shell git config --get remote.origin.url) IMAGE_SOURCE=$(shell git config --get remote.origin.url)
IMAGE_CREATED=$(shell date -u +%Y-%m-%dT%H:%M:%S%:z) EMPTY:=
SPACE:= $(EMPTY) $(EMPTY)
# MQ_VERSION_VRM is MQ_VERSION with only the Version, Release and Modifier fields (no Fix field). e.g. 9.1.2 instead of 9.1.2.0
MQ_VERSION_VRM=$(subst $(SPACE),.,$(wordlist 1,3,$(subst .,$(SPACE),$(MQ_VERSION))))
# Set variable if running on a Red Hat Enterprise Linux host
ifneq ($(wildcard /etc/redhat-release),)
REDHAT_RELEASE = $(shell cat /etc/redhat-release)
ifeq "$(findstring Red Hat,$(REDHAT_RELEASE))" "Red Hat"
RHEL_HOST = "true"
endif
endif
ifneq (,$(findstring Microsoft,$(shell uname -r))) ifneq (,$(findstring Microsoft,$(shell uname -r)))
DOWNLOADS_DIR=$(patsubst /mnt/c%,C:%,$(realpath ./downloads/)) DOWNLOADS_DIR=$(patsubst /mnt/c%,C:%,$(realpath ./downloads/))
else ifneq (,$(findstring Windows,$(shell echo ${OS})))
DOWNLOADS_DIR=$(shell pwd)/downloads/
else else
DOWNLOADS_DIR=$(realpath ./downloads/) DOWNLOADS_DIR=$(realpath ./downloads/)
endif endif
# Try to figure out which archive to use from the BASE_IMAGE
ifeq "$(findstring ubuntu,$(BASE_IMAGE))" "ubuntu"
MQ_ARCHIVE_TYPE=UBUNTU
MQ_ARCHIVE_DEV_PLATFORM=ubuntu
else
MQ_ARCHIVE_TYPE=LINUX
MQ_ARCHIVE_DEV_PLATFORM=linux
endif
# Try to figure out which archive to use from the architecture # Try to figure out which archive to use from the architecture
ifeq "$(ARCH)" "x86_64" ifeq "$(ARCH)" "amd64"
MQ_ARCHIVE_ARCH=X86-64 MQ_ARCHIVE_ARCH=X86-64
MQ_DEV_ARCH=x86-64 MQ_DEV_ARCH=x86-64
else ifeq "$(ARCH)" "ppc64le" else ifeq "$(ARCH)" "ppc64le"
@@ -91,15 +99,15 @@ else ifeq "$(ARCH)" "s390x"
MQ_DEV_ARCH=s390x MQ_DEV_ARCH=s390x
endif endif
# Archive names for IBM MQ Advanced for Developers # Archive names for IBM MQ Advanced for Developers
MQ_ARCHIVE_DEV_9.0.5.0=mqadv_dev905_$(MQ_ARCHIVE_DEV_PLATFORM)_x86-64.tar.gz
MQ_ARCHIVE_DEV_9.1.0.0=mqadv_dev910_$(MQ_ARCHIVE_DEV_PLATFORM)_$(MQ_DEV_ARCH).tar.gz MQ_ARCHIVE_DEV_9.1.0.0=mqadv_dev910_$(MQ_ARCHIVE_DEV_PLATFORM)_$(MQ_DEV_ARCH).tar.gz
MQ_ARCHIVE_DEV_9.1.1.0=mqadv_dev911_$(MQ_ARCHIVE_DEV_PLATFORM)_$(MQ_DEV_ARCH).tar.gz
MQ_ARCHIVE_DEV_9.1.2.0=mqadv_dev912_$(MQ_ARCHIVE_DEV_PLATFORM)_$(MQ_DEV_ARCH).tar.gz
############################################################################### ###############################################################################
# Build targets # Build targets
############################################################################### ###############################################################################
.PHONY: vars .PHONY: vars
vars: vars:
#ifeq "$(findstring ubuntu,$(BASE_IMAGE))","ubuntu"
@echo $(MQ_ARCHIVE_ARCH) @echo $(MQ_ARCHIVE_ARCH)
@echo $(MQ_ARCHIVE_TYPE) @echo $(MQ_ARCHIVE_TYPE)
@echo $(MQ_ARCHIVE) @echo $(MQ_ARCHIVE)
@@ -112,24 +120,15 @@ default: build-devserver test
all: build-devserver build-advancedserver all: build-devserver build-advancedserver
.PHONY: test-all .PHONY: test-all
test-all: test-devserver test-advancedserver test-all: build-devjmstest test-devserver test-advancedserver
.PHONY: precommit
precommit: fmt lint all test-all
.PHONY: devserver .PHONY: devserver
devserver: build-devserver test-devserver devserver: build-devserver build-devjmstest test-devserver
# Build incubating components # Build incubating components
.PHONY: incubating .PHONY: incubating
incubating: build-explorer incubating: build-explorer
.PHONY: clean
clean:
rm -rf ./coverage
rm -rf ./build
rm -rf ./deps
downloads/$(MQ_ARCHIVE_DEV): downloads/$(MQ_ARCHIVE_DEV):
$(info $(SPACER)$(shell printf $(TITLE)"Downloading IBM MQ Advanced for Developers "$(MQ_VERSION)$(END))) $(info $(SPACER)$(shell printf $(TITLE)"Downloading IBM MQ Advanced for Developers "$(MQ_VERSION)$(END)))
mkdir -p downloads mkdir -p downloads
@@ -138,24 +137,15 @@ downloads/$(MQ_ARCHIVE_DEV):
downloads/$(MQ_SDK_ARCHIVE): downloads/$(MQ_SDK_ARCHIVE):
$(info $(SPACER)$(shell printf $(TITLE)"Downloading IBM MQ Advanced for Developers "$(MQ_VERSION)$(END))) $(info $(SPACER)$(shell printf $(TITLE)"Downloading IBM MQ Advanced for Developers "$(MQ_VERSION)$(END)))
mkdir -p downloads mkdir -p downloads
cd downloads; curl -LO https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/$(MQ_SDK_ARCHIVE) cd downloads; curl -LO https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/$(MQ_SDK_ARCHIVE)
.PHONY: downloads .PHONY: downloads
downloads: downloads/$(MQ_ARCHIVE_DEV) downloads/$(MQ_SDK_ARCHIVE) downloads: downloads/$(MQ_ARCHIVE_DEV) downloads/$(MQ_SDK_ARCHIVE)
.PHONY: deps
deps:
glide install --strip-vendor
# Vendor Go dependencies for the Docker tests # Vendor Go dependencies for the Docker tests
test/docker/vendor: test/docker/vendor:
cd test/docker && dep ensure -vendor-only cd test/docker && dep ensure -vendor-only
.PHONY: build-cov
build-cov:
mkdir -p build
cd build; go test -c -covermode=count ../cmd/runmqserver
# Shortcut to just run the unit tests # Shortcut to just run the unit tests
.PHONY: test-unit .PHONY: test-unit
test-unit: test-unit:
@@ -163,8 +153,9 @@ test-unit:
.PHONY: test-advancedserver .PHONY: test-advancedserver
test-advancedserver: test/docker/vendor test-advancedserver: test/docker/vendor
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER) on $(shell docker --version)"$(END))) $(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) on $(shell docker --version)"$(END)))
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER) EXPECTED_LICENSE=Production go test -parallel $(NUM_CPU) $(TEST_OPTS_DOCKER) docker inspect $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG)
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) EXPECTED_LICENSE=Production go test -parallel $(NUM_CPU) $(TEST_OPTS_DOCKER)
.PHONY: build-devjmstest .PHONY: build-devjmstest
build-devjmstest: build-devjmstest:
@@ -173,15 +164,17 @@ build-devjmstest:
.PHONY: test-devserver .PHONY: test-devserver
test-devserver: test/docker/vendor test-devserver: test/docker/vendor
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_DEVSERVER) on $(shell docker --version)"$(END))) $(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_DEVSERVER):$(MQ_TAG) on $(shell docker --version)"$(END)))
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_DEVSERVER) EXPECTED_LICENSE=Developer DEV_JMS_IMAGE=$(DEV_JMS_IMAGE) go test -parallel $(NUM_CPU) -tags mqdev $(TEST_OPTS_DOCKER) docker inspect $(MQ_IMAGE_DEVSERVER):$(MQ_TAG)
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_DEVSERVER):$(MQ_TAG) EXPECTED_LICENSE=Developer DEV_JMS_IMAGE=$(DEV_JMS_IMAGE) IBMJRE=true go test -parallel $(NUM_CPU) -tags mqdev $(TEST_OPTS_DOCKER)
.PHONY: coverage
coverage: coverage:
mkdir coverage mkdir coverage
.PHONY: test-advancedserver-cover .PHONY: test-advancedserver-cover
test-advancedserver-cover: test/docker/vendor coverage test-advancedserver-cover: test/docker/vendor coverage
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER) with code coverage on $(shell docker --version)"$(END))) $(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) with code coverage on $(shell docker --version)"$(END)))
rm -f ./coverage/unit*.cov rm -f ./coverage/unit*.cov
# Run unit tests with coverage, for each package under 'internal' # Run unit tests with coverage, for each package under 'internal'
go list -f '{{.Name}}' ./internal/... | xargs -I {} go test -cover -covermode count -coverprofile ./coverage/unit-{}.cov ./internal/{} go list -f '{{.Name}}' ./internal/... | xargs -I {} go test -cover -covermode count -coverprofile ./coverage/unit-{}.cov ./internal/{}
@@ -193,7 +186,7 @@ test-advancedserver-cover: test/docker/vendor coverage
rm -f ./test/docker/coverage/*.cov rm -f ./test/docker/coverage/*.cov
rm -f ./coverage/docker.* rm -f ./coverage/docker.*
mkdir -p ./test/docker/coverage/ mkdir -p ./test/docker/coverage/
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER)-cover TEST_COVER=true go test $(TEST_OPTS_DOCKER) cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG)-cover TEST_COVER=true go test $(TEST_OPTS_DOCKER)
echo 'mode: count' > ./coverage/docker.cov echo 'mode: count' > ./coverage/docker.cov
tail -q -n +2 ./test/docker/coverage/*.cov >> ./coverage/docker.cov tail -q -n +2 ./test/docker/coverage/*.cov >> ./coverage/docker.cov
go tool cover -html=./coverage/docker.cov -o ./coverage/docker.html go tool cover -html=./coverage/docker.cov -o ./coverage/docker.html
@@ -202,7 +195,7 @@ test-advancedserver-cover: test/docker/vendor coverage
tail -q -n +2 ./coverage/unit.cov ./coverage/docker.cov >> ./coverage/combined.cov tail -q -n +2 ./coverage/unit.cov ./coverage/docker.cov >> ./coverage/combined.cov
go tool cover -html=./coverage/combined.cov -o ./coverage/combined.html go tool cover -html=./coverage/combined.cov -o ./coverage/combined.html
define docker-build-mq define build-mq
# Create a temporary network to use for the build # Create a temporary network to use for the build
$(DOCKER) network create build $(DOCKER) network create build
# Start a web server to host the MQ downloadable (tar.gz) file # Start a web server to host the MQ downloadable (tar.gz) file
@@ -213,25 +206,52 @@ define docker-build-mq
--network-alias build \ --network-alias build \
--volume $(DOWNLOADS_DIR):/usr/share/nginx/html:ro \ --volume $(DOWNLOADS_DIR):/usr/share/nginx/html:ro \
--detach \ --detach \
nginx:alpine docker.io/nginx:alpine
# Build the new image # Build the new image
$(DOCKER) build \ $(DOCKER) build \
--tag $1 \ --tag $1:$2 \
--file $2 \ --file $3 \
--network build \ --network build \
--build-arg MQ_URL=http://build:80/$3 \ --build-arg MQ_URL=http://build:80/$4 \
--build-arg BASE_IMAGE=$(BASE_IMAGE) \
--build-arg BUILDER_IMAGE=$(MQ_IMAGE_GOLANG_SDK) \
--build-arg IMAGE_REVISION="$(IMAGE_REVISION)" \
--build-arg IMAGE_CREATED="$(IMAGE_CREATED)" \
--build-arg IMAGE_SOURCE="$(IMAGE_SOURCE)" \
--label IBM_PRODUCT_ID=$4 \
--label IBM_PRODUCT_NAME=$5 \
--label IBM_PRODUCT_VERSION=$6 \
--build-arg MQ_PACKAGES="$(MQ_PACKAGES)" \ --build-arg MQ_PACKAGES="$(MQ_PACKAGES)" \
--build-arg IMAGE_REVISION="$(IMAGE_REVISION)" \
--build-arg IMAGE_SOURCE="$(IMAGE_SOURCE)" \
--build-arg IMAGE_TAG="$1:$2" \
--build-arg MQM_UID=$(MQM_UID) \
--label version=$(MQ_VERSION) \
--label name=$1 \
--label build-date=$(shell date +%Y-%m-%dT%H:%M:%S%z) \
--label release="$(RELEASE)" \
--label architecture="$(ARCH)" \
--label run="docker run -d -e LICENSE=accept $1:$2" \
--label vcs-ref=$(IMAGE_REVISION) \
--label vcs-type=git \
--label vcs-url=$(IMAGE_SOURCE) \
--target $5 \
. ; $(DOCKER) kill $(BUILD_SERVER_CONTAINER) && $(DOCKER) network rm build . ; $(DOCKER) kill $(BUILD_SERVER_CONTAINER) && $(DOCKER) network rm build
endef endef
define build-mq-ctr
buildah/mq-buildah $1 $2 \
--file /src/Dockerfile-server \
--build-arg MQ_URL="file:///src/downloads/$3" \
--build-arg MQ_PACKAGES="$(MQ_PACKAGES)" \
--build-arg IMAGE_REVISION="$(IMAGE_REVISION)" \
--build-arg IMAGE_SOURCE="$(IMAGE_SOURCE)" \
--build-arg IMAGE_TAG="$1:$2" \
--build-arg MQM_UID=$(MQM_UID) \
--label version=$(MQ_VERSION) \
--label name=$1 \
--label build-date=$(shell date +%Y-%m-%dT%H:%M:%S%z) \
--label release="$(RELEASE)" \
--label architecture="$(ARCH)" \
--label run="docker run -d -e LICENSE=accept $1:$2" \
--label vcs-ref=$(IMAGE_REVISION) \
--label vcs-type=git \
--label vcs-url=$(IMAGE_SOURCE) \
--target $4
endef
DOCKER_SERVER_VERSION=$(shell docker version --format "{{ .Server.Version }}") DOCKER_SERVER_VERSION=$(shell docker version --format "{{ .Server.Version }}")
DOCKER_CLIENT_VERSION=$(shell docker version --format "{{ .Client.Version }}") DOCKER_CLIENT_VERSION=$(shell docker version --format "{{ .Client.Version }}")
.PHONY: docker-version .PHONY: docker-version
@@ -240,55 +260,116 @@ docker-version:
@test "$(word 1,$(subst ., ,$(DOCKER_SERVER_VERSION)))" -ge "17" || ("$(word 1,$(subst ., ,$(DOCKER_SERVER_VERSION)))" -eq "17" && "$(word 2,$(subst ., ,$(DOCKER_CLIENT_VERSION)))" -ge "05") || (echo "Error: Docker server 17.05 or greater is required" && exit 1) @test "$(word 1,$(subst ., ,$(DOCKER_SERVER_VERSION)))" -ge "17" || ("$(word 1,$(subst ., ,$(DOCKER_SERVER_VERSION)))" -eq "17" && "$(word 2,$(subst ., ,$(DOCKER_CLIENT_VERSION)))" -ge "05") || (echo "Error: Docker server 17.05 or greater is required" && exit 1)
.PHONY: build-advancedserver .PHONY: build-advancedserver
build-advancedserver: MQ_SDK_ARCHIVE=$(MQ_ARCHIVE) ifdef RHEL_HOST
build-advancedserver: downloads/$(MQ_ARCHIVE) docker-version build-golang-sdk # Build using Buildah inside a container on RHEL hosts
$(info $(SPACER)$(shell printf $(TITLE)"Build $(MQ_IMAGE_ADVANCEDSERVER)"$(END))) build-advancedserver: build-advancedserver-ctr
$(call docker-build-mq,$(MQ_IMAGE_ADVANCEDSERVER),Dockerfile-server,$(MQ_ARCHIVE),"4486e8c4cc9146fd9b3ce1f14a2dfc5b","IBM MQ Advanced",$(MQ_VERSION)) else
build-advancedserver: build-advancedserver-host
endif
.PHONY: build-advancedserver-host
build-advancedserver-host: downloads/$(MQ_ARCHIVE) docker-version
$(info $(SPACER)$(shell printf $(TITLE)"Build $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG)"$(END)))
$(call build-mq,$(MQ_IMAGE_ADVANCEDSERVER),$(MQ_TAG),Dockerfile-server,$(MQ_ARCHIVE),mq-server)
.PHONY: build-advancedserver-ctr
build-advancedserver-ctr: downloads/$(MQ_ARCHIVE)
$(info $(shell printf $(TITLE)"Build $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) in a container"$(END)))
$(call build-mq-ctr,$(MQ_IMAGE_ADVANCEDSERVER),$(MQ_TAG),$(MQ_ARCHIVE),mq-server)
.PHONY: build-devserver .PHONY: build-devserver
# Target-specific variable to add web server into devserver image ifdef RHEL_HOST
ifeq "$(findstring ubuntu,$(BASE_IMAGE))" "ubuntu" # Build using Buildah inside a container on RHEL hosts
build-devserver: MQ_PACKAGES=ibmmq-server ibmmq-java ibmmq-jre ibmmq-gskit ibmmq-msg-.* ibmmq-samples ibmmq-ams ibmmq-web build-devserver: build-devserver-ctr
else else
build-devserver: MQ_PACKAGES=MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesAMS-*.rpm MQSeriesWeb-*.rpm build-devserver: build-devserver-host
endif endif
build-devserver: MQ_SDK_ARCHIVE=$(MQ_ARCHIVE_DEV)
build-devserver: downloads/$(MQ_ARCHIVE_DEV) docker-version build-golang-sdk .PHONY: build-devserver-host
$(info $(shell printf $(TITLE)"Build $(MQ_IMAGE_DEVSERVER_BASE)"$(END))) build-devserver-host: downloads/$(MQ_ARCHIVE_DEV) docker-version
$(call docker-build-mq,$(MQ_IMAGE_DEVSERVER_BASE),Dockerfile-server,$(MQ_ARCHIVE_DEV),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)",$(MQ_VERSION)) $(info $(shell printf $(TITLE)"Build $(MQ_IMAGE_DEVSERVER):$(MQ_TAG)"$(END)))
$(DOCKER) build --tag $(MQ_IMAGE_DEVSERVER) --build-arg IMAGE_SOURCE="$(IMAGE_SOURCE)" --build-arg IMAGE_REVISION="$(IMAGE_REVISION)" --build-arg IMAGE_CREATED="$(IMAGE_CREATED)" --build-arg BASE_IMAGE=$(MQ_IMAGE_DEVSERVER_BASE) --build-arg BUILDER_IMAGE=$(MQ_IMAGE_GOLANG_SDK) --file incubating/mqadvanced-server-dev/Dockerfile . $(call build-mq,$(MQ_IMAGE_DEVSERVER),$(MQ_TAG),Dockerfile-server,$(MQ_ARCHIVE_DEV),mq-dev-server)
.PHONY: build-devserver-ctr
build-devserver-ctr: downloads/$(MQ_ARCHIVE_DEV)
$(info $(shell printf $(TITLE)"Build $(MQ_IMAGE_DEVSERVER):$(MQ_TAG) in a container"$(END)))
$(call build-mq-ctr,$(MQ_IMAGE_DEVSERVER),$(MQ_TAG),$(MQ_ARCHIVE_DEV),mq-dev-server)
.PHONY: build-advancedserver-cover .PHONY: build-advancedserver-cover
build-advancedserver-cover: docker-version build-advancedserver-cover: docker-version
$(DOCKER) build --build-arg BASE_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER) -t $(MQ_IMAGE_ADVANCEDSERVER)-cover -f Dockerfile-server.cover . $(DOCKER) build --build-arg BASE_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) -t $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG)-cover -f Dockerfile-server.cover .
.PHONY: build-explorer docker-pull .PHONY: build-explorer
build-explorer: downloads/$(MQ_ARCHIVE_DEV) build-explorer: downloads/$(MQ_ARCHIVE_DEV)
$(call docker-build-mq,mq-explorer:latest-$(ARCH),incubating/mq-explorer/Dockerfile-mq-explorer,$(MQ_ARCHIVE_DEV),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)",$(MQ_VERSION)) $(call build-mq,mq-explorer,latest-$(ARCH),incubating/mq-explorer/Dockerfile,$(MQ_ARCHIVE_DEV),mq-explorer)
ifeq "$(findstring ubuntu,$(BASE_IMAGE))" "ubuntu" .PHONY: build-sdk
build-sdk: MQ_PACKAGES=ibmmq-sdk ibmmq-samples build-essential build-sdk: downloads/$(MQ_ARCHIVE_DEV)
else $(info $(shell printf $(TITLE)"Build $(MQ_IMAGE_SDK)"$(END)))
build-sdk: MQ_PACKAGES=MQSeriesRuntime-*.rpm MQSeriesSDK-*.rpm MQSeriesSamples*.rpm $(call build-mq,mq-sdk,$(MQ_TAG),incubating/mq-sdk/Dockerfile,$(MQ_SDK_ARCHIVE),mq-sdk)
endif
build-sdk: downloads/$(MQ_SDK_ARCHIVE) docker-version docker-pull
$(call docker-build-mq,$(MQ_IMAGE_SDK),incubating/mq-sdk/Dockerfile,$(MQ_SDK_ARCHIVE),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers SDK (Non-Warranted)",$(MQ_VERSION))
build-golang-sdk: downloads/$(MQ_SDK_ARCHIVE) docker-version build-sdk .PHONY: debug-vars
$(DOCKER) build --build-arg BASE_IMAGE=$(MQ_IMAGE_SDK) -t $(MQ_IMAGE_GOLANG_SDK) -f incubating/mq-golang-sdk/Dockerfile . debug-vars:
# $(call docker-build-mq,$(MQ_IMAGE_GOLANG_SDK),incubating/mq-golang-sdk/Dockerfile,$(MQ_IMAGE_SDK),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers SDK (Non-Warranted)",$(MQ_VERSION)) @echo MQ_VERSION=$(MQ_VERSION)
@echo MQ_VERSION_VRM=$(MQ_VERSION_VRM)
@echo MQ_ARCHIVE=$(MQ_ARCHIVE)
@echo MQ_IMAGE_DEVSERVER=$(MQ_IMAGE_DEVSERVER)
@echo MQ_IMAGE_ADVANCEDSERVER=$(MQ_IMAGE_ADVANCEDSERVER)
docker-pull: include formatting.mk
$(DOCKER) pull $(BASE_IMAGE)
GO_PKG_DIRS = ./cmd ./internal ./test .PHONY: clean
clean:
rm -rf ./coverage
rm -rf ./build
rm -rf ./deps
.PHONY: deps
deps:
glide install --strip-vendor
.PHONY: build-cov
build-cov:
mkdir -p build
cd build; go test -c -covermode=count ../cmd/runmqserver
.PHONY: precommit
precommit: fmt lint
.PHONY: fmt
fmt: $(addsuffix /$(wildcard *.go), $(GO_PKG_DIRS)) fmt: $(addsuffix /$(wildcard *.go), $(GO_PKG_DIRS))
go fmt $(addsuffix /..., $(GO_PKG_DIRS)) go fmt $(addsuffix /..., $(GO_PKG_DIRS))
.PHONY: lint
lint: $(addsuffix /$(wildcard *.go), $(GO_PKG_DIRS)) lint: $(addsuffix /$(wildcard *.go), $(GO_PKG_DIRS))
@# This expression is necessary because /... includes the vendor directory in golint @# This expression is necessary because /... includes the vendor directory in golint
@# As of 11/04/2018 there is an open issue to fix it: https://github.com/golang/lint/issues/320 @# As of 11/04/2018 there is an open issue to fix it: https://github.com/golang/lint/issues/320
golint -set_exit_status $(sort $(dir $(wildcard $(addsuffix /*/*.go, $(GO_PKG_DIRS))))) golint -set_exit_status $(sort $(dir $(wildcard $(addsuffix /*/*.go, $(GO_PKG_DIRS)))))
.PHONY: gosec
gosec: $(info $(SPACER)$(shell printf "Running gosec test"$(END)))
@gosec -fmt=json -out=gosec_results.json cmd/... internal/... 2> /dev/null ;\
cat "gosec_results.json" ;\
cat gosec_results.json | grep HIGH | grep severity > /dev/null ;\
if [ $$? -eq 0 ]; then \
printf "\nFAILURE: gosec found files containing HIGH severity issues - see results.json\n" ;\
exit 1 ;\
else \
printf "\ngosec found no HIGH severity issues\n" ;\
fi ;\
cat gosec_results.json | grep MEDIUM | grep severity > /dev/null ;\
if [ $$? -eq 0 ]; then \
printf "\nFAILURE: gosec found files containing MEDIUM severity issues - see results.json\n" ;\
exit 1 ;\
else \
printf "\ngosec found no MEDIUM severity issues\n" ;\
fi ;\
cat gosec_results.json | grep LOW | grep severity > /dev/null;\
if [ $$? -eq 0 ]; then \
printf "\nFAILURE: gosec found files containing LOW severity issues - see results.json\n" ;\
exit 1;\
else \
printf "\ngosec found no LOW severity issues\n" ;\
fi ;\
include formatting.mk include formatting.mk

View File

@@ -1,26 +1,27 @@
![IBM MQ logo](https://developer.ibm.com/messaging/wp-content/uploads/sites/18/2017/07/IBM-MQ-Square-200.png) # IBM MQ container
# Overview [![Build Status](https://travis-ci.org/ibm-messaging/mq-container.svg?branch=master)](https://travis-ci.org/ibm-messaging/mq-container)
**Note**: The `master` branch may be in an *unstable or even broken state* during development.
To get a stable version, please use the correct [branch](https://github.com/ibm-messaging/mq-container/branches) for your MQ version, instead of the `master` branch.
## Overview
Run [IBM® MQ](http://www-03.ibm.com/software/products/en/ibm-mq) in a container. Run [IBM® MQ](http://www-03.ibm.com/software/products/en/ibm-mq) in a container.
You can build an image containing either IBM MQ Advanced, or IBM MQ Advanced for Developers. The developer image includes a [default developer configuration](docs/developer-config.md), to make it easier to get started. There is also an [incubating](incubating) folder for additional images for other MQ components, which you might find useful. You can build an image containing either IBM MQ Advanced, or IBM MQ Advanced for Developers. The developer image includes a [default developer configuration](docs/developer-config.md), to make it easier to get started. There is also an [incubating](incubating) folder for additional images for other MQ components, which you might find useful.
# Current status ## Build
MQ Advanced for Developers image [![Build Status](https://travis-ci.org/ibm-messaging/mq-container.svg?branch=master)](https://travis-ci.org/ibm-messaging/mq-container)
# Build
After extracting the code from this repository, you can follow the [build documentation](docs/building.md) to build an image. After extracting the code from this repository, you can follow the [build documentation](docs/building.md) to build an image.
# Usage ## Usage
See the [usage documentation](docs/usage.md) for details on how to run a container. See the [usage documentation](docs/usage.md) for details on how to run a container.
Note that in order to use the image, it is necessary to accept the terms of the [IBM MQ license](#license). Note that in order to use the image, it is necessary to accept the terms of the [IBM MQ license](#license).
## Environment variables supported by this image ### Environment variables supported by this image
- **LICENSE** - Set this to `accept` to agree to the MQ Advanced for Developers license. If you wish to see the license you can set this to `view`. - **LICENSE** - Set this to `accept` to agree to the MQ Advanced for Developers license. If you wish to see the license you can set this to `view`.
- **LANG** - Set this to the language you would like the license to be printed in. - **LANG** - Set this to the language you would like the license to be printed in.
@@ -30,25 +31,25 @@ Note that in order to use the image, it is necessary to accept the terms of the
See the [default developer configuration docs](docs/developer-config.md) for the extra environment variables supported by the MQ Advanced for Developers image. See the [default developer configuration docs](docs/developer-config.md) for the extra environment variables supported by the MQ Advanced for Developers image.
## Kubernetes ### Kubernetes
If you want to use IBM MQ in [Kubernetes](https://kubernetes.io), you can find an example [Helm](https://helm.sh/) chart here: [IBM charts](https://github.com/IBM/charts). This can be used to run the container on a cluster, such as [IBM Cloud Private](https://www.ibm.com/cloud-computing/products/ibm-cloud-private/) or the [IBM Cloud Kubernetes Service](https://www.ibm.com/cloud/container-service). If you want to use IBM MQ in [Kubernetes](https://kubernetes.io), you can find an example [Helm](https://helm.sh/) chart here: [IBM charts](https://github.com/IBM/charts). This can be used to run the container on a cluster, such as [IBM Cloud Private](https://www.ibm.com/cloud-computing/products/ibm-cloud-private/) or the [IBM Cloud Kubernetes Service](https://www.ibm.com/cloud/container-service).
# Issues and contributions ## Issues and contributions
For issues relating specifically to the container image or Helm chart, please use the [GitHub issue tracker](https://github.com/ibm-messaging/mq-container/issues). If you do submit a Pull Request related to this Docker image, please indicate in the Pull Request that you accept and agree to be bound by the terms of the [IBM Contributor License Agreement](CLA.md). For issues relating specifically to the container image or Helm chart, please use the [GitHub issue tracker](https://github.com/ibm-messaging/mq-container/issues). If you do submit a Pull Request related to this Docker image, please indicate in the Pull Request that you accept and agree to be bound by the terms of the [IBM Contributor License Agreement](CLA.md).
# License ## License
The Dockerfiles and associated code and scripts are licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). The Dockerfiles and associated code and scripts are licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).
Licenses for the products installed within the images are as follows: Licenses for the products installed within the images are as follows:
- [IBM MQ Advanced for Developers](http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?la_formnum=Z125-3301-14&li_formnum=L-APIG-AKHJY4) (International License Agreement for Non-Warranted Programs). This license may be viewed from an image using the `LICENSE=view` environment variable as described above or by following the link above. - [IBM MQ Advanced for Developers](http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?la_formnum=Z125-3301-14&li_formnum=L-APIG-AVCJ4S) (International License Agreement for Non-Warranted Programs). This license may be viewed from an image using the `LICENSE=view` environment variable as described above or by following the link above.
- [IBM MQ Advanced](http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?la_formnum=Z125-3301-14&li_formnum=L-APIG-AKHJJP) (International Program License Agreement). This license may be viewed from an image using the `LICENSE=view` environment variable as described above or by following the link above. - [IBM MQ Advanced](http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?la_formnum=Z125-3301-14&li_formnum=L-APIG-AZYF4X) (International Program License Agreement). This license may be viewed from an image using the `LICENSE=view` environment variable as described above or by following the link above.
- License information for Ubuntu packages may be found in `/usr/share/doc/${package}/copyright` - License information for Ubuntu packages may be found in `/usr/share/doc/${package}/copyright`
Note: The IBM MQ Advanced for Developers license does not permit further distribution and the terms restrict usage to a developer machine. Note: The IBM MQ Advanced for Developers license does not permit further distribution and the terms restrict usage to a developer machine.
# Copyright ## Copyright
© Copyright IBM Corporation 2015, 2018 © Copyright IBM Corporation 2015, 2019

23
buildah/Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
# © Copyright IBM Corporation 2019
#
# 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.
# Fedora includes more recent versions of buildah (need buildah V1.7 to get
# multi-stage builds to work properly)
FROM docker.io/fedora:29
RUN yum install -y buildah
COPY build.sh /usr/local/bin/build
RUN chmod +x /usr/local/bin/build
ENV STORAGE_DRIVER=vfs
ENV BUILDAH_ISOLATION=chroot
ENTRYPOINT ["build"]

41
buildah/build.sh Normal file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2019
#
#
# 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.
# Build a container image from a Dockerfile using Buildah
# If the Docker socket is available, the image will be pushed to Docker once built.
readonly IMAGE=$1:$2
shift
shift
readonly SRC="/src"
readonly OCI_DIR="/var/oci"
echo "****************************************"
echo " Inside the Buildah container"
echo "****************************************"
set -ex
# Build using the supplied options. Always pass the source directory in, and
# use it as the build context
buildah build-using-dockerfile --tag ${IMAGE} --volume /src:/src "$@" /src
if [ -e ${OCI_DIR} ]; then
buildah push ${IMAGE} oci-archive:${OCI_DIR}/${IMAGE}
fi
if [ -e /var/run/docker.sock ]; then
buildah push ${IMAGE} docker-daemon:${IMAGE}
fi

51
buildah/mq-buildah Executable file
View File

@@ -0,0 +1,51 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2019
#
#
# 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.
# Build and run a container image with Buildah installed
if [ $# -eq 0 ]; then
echo "Usage: $0 imageName imageTag buildah-options"
exit 1
fi
readonly SCRIPT_DIR="$( cd "$(dirname "$0")" ; pwd -P )"
readonly DIR=${SCRIPT_DIR}/..
readonly BUILDAH_IMAGE=mq-buildah
RUN_OPTS="--volume ${DIR}:/src --cap-add SYS_ADMIN --rm --interactive --tty"
CMD="docker"
# If Docker is installed, then map the Docker socket into the container, to
# allow buildah to push the resulting image into Docker's image store
# WARNING: This means that the buildah container can do anything the "docker"
# can do.
command -v docker
if [[ $? == 0 && -e /var/run/docker.sock ]]; then
RUN_OPTS="${RUN_OPTS} --volume /var/run/docker.sock:/var/run/docker.sock"
fi
command -v podman
if [[ $? -eq 0 ]]; then
CMD="podman"
OCI_DIR="/tmp/mq-buildah"
mkdir -p ${OCI_DIR}
RUN_OPTS="${RUN_OPTS} --volume ${OCI_DIR}:/var/oci"
echo "Image archives will be written to ${OCI_DIR}"
fi
${CMD} build --tag ${BUILDAH_IMAGE} --file ${SCRIPT_DIR}/Dockerfile ${SCRIPT_DIR}
set -x
${CMD} run ${RUN_OPTS} ${BUILDAH_IMAGE} "$@"

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2017 © Copyright IBM Corporation 2017, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ func queueManagerHealthy() (bool, error) {
return false, err return false, err
} }
// Specify the queue manager name, just in case someone's created a second queue manager // Specify the queue manager name, just in case someone's created a second queue manager
// #nosec G204
cmd := exec.Command("dspmq", "-n", "-m", name) cmd := exec.Command("dspmq", "-n", "-m", name)
// Run the command and wait for completion // Run the command and wait for completion
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
@@ -39,8 +40,8 @@ func queueManagerHealthy() (bool, error) {
fmt.Println(err) fmt.Println(err)
return false, err return false, err
} }
fmt.Println(out) fmt.Printf("%s", out)
if !strings.Contains(string(out), "(RUNNING)") { if !strings.Contains(string(out), "(RUNNING)") && !strings.Contains(string(out), "(RUNNING AS STANDBY)") && !strings.Contains(string(out), "(STARTING)") {
return false, nil return false, nil
} }
return true, nil return true, nil

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2017, 2018 © Copyright IBM Corporation 2017, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import (
"net" "net"
"os" "os"
"github.com/ibm-messaging/mq-container/internal/name"
"github.com/ibm-messaging/mq-container/internal/ready" "github.com/ibm-messaging/mq-container/internal/ready"
) )
@@ -31,11 +32,25 @@ func main() {
if !r || err != nil { if !r || err != nil {
os.Exit(1) os.Exit(1)
} }
// Check if the queue manager has a running listener name, err := name.GetQueueManagerName()
conn, err := net.Dial("tcp", "127.0.0.1:1414")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
conn.Close()
// Check if the queue manager has a running listener
if standby, _ := ready.IsRunningAsStandbyQM(name); !standby {
conn, err := net.Dial("tcp", "127.0.0.1:1414")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = conn.Close()
if err != nil {
fmt.Println(err)
}
} else {
fmt.Printf("Detected queue manager running in standby mode")
os.Exit(10)
}
} }

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -23,23 +23,30 @@ import (
"syscall" "syscall"
"github.com/ibm-messaging/mq-container/internal/command" "github.com/ibm-messaging/mq-container/internal/command"
"github.com/ibm-messaging/mq-container/internal/containerruntimelogger"
"github.com/ibm-messaging/mq-container/internal/logger" "github.com/ibm-messaging/mq-container/internal/logger"
"github.com/ibm-messaging/mq-container/internal/mqtemplate"
"github.com/ibm-messaging/mq-container/internal/name" "github.com/ibm-messaging/mq-container/internal/name"
) )
var log *logger.Logger var log *logger.Logger
func setPassword(user string, password string) error { func setPassword(user string, password string) error {
cmd := exec.Command("chpasswd") // #nosec G204
cmd := exec.Command("sudo", "chpasswd")
stdin, err := cmd.StdinPipe() stdin, err := cmd.StdinPipe()
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintf(stdin, "%s:%s", user, password) fmt.Fprintf(stdin, "%s:%s", user, password)
stdin.Close() err = stdin.Close()
_, _, err = command.RunCmd(cmd)
if err != nil { if err != nil {
return err log.Errorf("Error closing password stdin: %v", err)
}
out, _, err := command.RunCmd(cmd)
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) log.Printf("Set password for \"%v\" user", user)
return nil return nil
@@ -85,20 +92,20 @@ func configureLogger() error {
func configureWeb(qmName string) error { func configureWeb(qmName string) error {
out := "/etc/mqm/web/installations/Installation1/angular.persistence/admin.json" out := "/etc/mqm/web/installations/Installation1/angular.persistence/admin.json"
return processTemplateFile("/etc/mqm/admin.json.tpl", out, map[string]string{"QueueManagerName": qmName}) return mqtemplate.ProcessTemplateFile("/etc/mqm/admin.json.tpl", out, map[string]string{"QueueManagerName": qmName}, log)
} }
func logTerminationf(format string, args ...interface{}) { func logTerminationf(format string, args ...interface{}) {
logTermination(fmt.Sprintf(format, args)) logTermination(fmt.Sprintf(format, args...))
} }
// TODO: Duplicated code // TODO: Duplicated code
func logTermination(args ...interface{}) { func logTermination(args ...interface{}) {
msg := fmt.Sprint(args) msg := fmt.Sprint(args...)
// Write the message to the termination log. This is the default place // Write the message to the termination log. This is not the default place
// that Kubernetes will look for termination information. // that Kubernetes will look for termination information.
log.Debugf("Writing termination message: %v", msg) log.Debugf("Writing termination message: %v", msg)
err := ioutil.WriteFile("/dev/termination-log", []byte(msg), 0660) err := ioutil.WriteFile("/run/termination-log", []byte(msg), 0660)
if err != nil { if err != nil {
log.Debug(err) log.Debug(err)
} }
@@ -111,6 +118,13 @@ func doMain() error {
logTermination(err) logTermination(err)
return err return err
} }
err = containerruntimelogger.LogContainerDetails(log)
if err != nil {
logTermination(err)
return err
}
adminPassword, set := os.LookupEnv("MQ_ADMIN_PASSWORD") adminPassword, set := os.LookupEnv("MQ_ADMIN_PASSWORD")
if set { if set {
err = setPassword("admin", adminPassword) err = setPassword("admin", adminPassword)
@@ -139,14 +153,6 @@ func doMain() error {
logTerminationf("Error getting queue manager name: %v", err) logTerminationf("Error getting queue manager name: %v", err)
return err return err
} }
ks, set := os.LookupEnv("MQ_TLS_KEYSTORE")
if set {
err = configureTLS(name, ks, os.Getenv("MQ_TLS_PASSPHRASE"))
if err != nil {
logTerminationf("Error configuring TLS: %v", err)
return err
}
}
err = configureWeb(name) err = configureWeb(name)
if err != nil { if err != nil {
@@ -165,6 +171,10 @@ func main() {
osExit(1) osExit(1)
} else { } else {
// Replace this process with runmqserver // Replace this process with runmqserver
syscall.Exec("/usr/local/bin/runmqserver", []string{"runmqserver"}, os.Environ()) // #nosec G204
err = syscall.Exec("/usr/local/bin/runmqserver", []string{"runmqserver", "-dev"}, os.Environ())
if err != nil {
log.Errorf("Error replacing this process with runmqserver: %v", err)
}
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@ package main
import ( import (
"os" "os"
"github.com/ibm-messaging/mq-container/internal/mqtemplate"
) )
func updateMQSC(appPasswordRequired bool) error { func updateMQSC(appPasswordRequired bool) error {
@@ -30,12 +32,19 @@ func updateMQSC(appPasswordRequired bool) error {
if os.Getenv("MQ_DEV") == "true" { if os.Getenv("MQ_DEV") == "true" {
const mqscTemplate string = mqsc + ".tpl" const mqscTemplate string = mqsc + ".tpl"
// Re-configure channel if app password not set // Re-configure channel if app password not set
err := processTemplateFile(mqsc+".tpl", mqsc, map[string]string{"ChckClnt": checkClient}) err := mqtemplate.ProcessTemplateFile(mqsc+".tpl", mqsc, map[string]string{"ChckClnt": checkClient}, log)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
os.Remove(mqsc) _, err := os.Stat(mqsc)
if !os.IsNotExist(err) {
err = os.Remove(mqsc)
if err != nil {
log.Errorf("Error removing file %s: %v", mqsc, err)
return err
}
}
} }
return nil return nil
} }

View File

@@ -1,163 +0,0 @@
/*
© Copyright IBM Corporation 2018
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 main
import (
"fmt"
"os"
"path/filepath"
"github.com/ibm-messaging/mq-container/internal/command"
)
func configureWebTLS(cms *KeyStore) error {
dir := "/run/runmqdevserver/tls"
ks := NewJKSKeyStore(filepath.Join(dir, "key.jks"), cms.Password)
ts := NewJKSKeyStore(filepath.Join(dir, "trust.jks"), cms.Password)
log.Debug("Creating key store")
err := ks.Create()
if err != nil {
return err
}
log.Debug("Creating trust store")
err = ts.Create()
if err != nil {
return err
}
log.Debug("Importing keys")
err = ks.Import(cms.Filename, cms.Password)
if err != nil {
return err
}
webConfigDir := "/etc/mqm/web/installations/Installation1/servers/mqweb"
tlsConfig := filepath.Join(webConfigDir, "tls.xml")
newTLSConfig := filepath.Join(webConfigDir, "tls-dev.xml")
err = os.Remove(tlsConfig)
if err != nil {
return err
}
// we symlink here to prevent issues on restart
err = os.Symlink(newTLSConfig, tlsConfig)
if err != nil {
return err
}
mqmUID, mqmGID, err := command.LookupMQM()
if err != nil {
log.Error(err)
return err
}
err = os.Chown(tlsConfig, mqmUID, mqmGID)
if err != nil {
log.Error(err)
return err
}
return nil
}
func configureTLS(qmName string, inputFile string, passPhrase string) error {
log.Debug("Configuring TLS")
_, err := os.Stat(inputFile)
if err != nil {
return err
}
// TODO: Use a persisted file (on the volume) instead?
dir := "/run/runmqdevserver/tls"
keyFile := filepath.Join(dir, "key.kdb")
_, err = os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(dir, 0770)
if err != nil {
return err
}
mqmUID, mqmGID, err := command.LookupMQM()
if err != nil {
log.Error(err)
return err
}
err = os.Chown(dir, mqmUID, mqmGID)
if err != nil {
log.Error(err)
return err
}
} else {
return err
}
}
cms := NewCMSKeyStore(keyFile, passPhrase)
err = cms.Create()
if err != nil {
return err
}
err = cms.CreateStash()
if err != nil {
return err
}
err = cms.Import(inputFile, passPhrase)
if err != nil {
return err
}
labels, err := cms.GetCertificateLabels()
if err != nil {
return err
}
if len(labels) == 0 {
return fmt.Errorf("unable to find certificate label")
}
log.Debugf("Renaming certificate from %v", labels[0])
const newLabel string = "devcert"
err = cms.RenameCertificate(labels[0], newLabel)
if err != nil {
return err
}
var sslCipherSpec string
if os.Getenv("MQ_DEV") == "true" {
sslCipherSpec = "TLS_RSA_WITH_AES_128_CBC_SHA256"
} else {
sslCipherSpec = "' '"
}
const mqsc string = "/etc/mqm/20-dev-tls.mqsc"
const mqscTemplate string = mqsc + ".tpl"
err = processTemplateFile(mqscTemplate, mqsc, map[string]string{
"SSLKeyR": filepath.Join(dir, "key"),
"CertificateLabel": newLabel,
"SSLCipherSpec": sslCipherSpec,
})
if err != nil {
return err
}
err = configureWebTLS(cms)
if err != nil {
return err
}
return nil
}

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2017, 2018 © Copyright IBM Corporation 2017, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -17,18 +17,17 @@ package main
import ( import (
"os" "os"
"path/filepath"
"runtime" "runtime"
"syscall" "syscall"
"github.com/ibm-messaging/mq-container/internal/command" "github.com/ibm-messaging/mq-container/internal/command"
) )
func createVolume(path string) error { func createVolume(dataPath string) error {
dataPath := filepath.Join(path, "data")
fi, err := os.Stat(dataPath) fi, err := os.Stat(dataPath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
// #nosec G301
err = os.MkdirAll(dataPath, 0755) err = os.MkdirAll(dataPath, 0755)
if err != nil { if err != nil {
return err return err

View File

@@ -78,6 +78,7 @@ func checkLicense() (bool, error) {
return true, nil return true, nil
case ok && lic == "view": case ok && lic == "view":
file := filepath.Join("/opt/mqm/licenses", resolveLicenseFile()) file := filepath.Join("/opt/mqm/licenses", resolveLicenseFile())
// #nosec G304
buf, err := ioutil.ReadFile(file) buf, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
log.Println(err) log.Println(err)

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2017, 2018 © Copyright IBM Corporation 2017, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -21,9 +21,12 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings"
"sync" "sync"
"github.com/ibm-messaging/mq-container/internal/command"
"github.com/ibm-messaging/mq-container/internal/logger" "github.com/ibm-messaging/mq-container/internal/logger"
"github.com/ibm-messaging/mq-container/internal/mqini" "github.com/ibm-messaging/mq-container/internal/mqini"
) )
@@ -31,20 +34,26 @@ import (
// var debug = false // var debug = false
var log *logger.Logger var log *logger.Logger
var collectDiagOnFail = false
func logTerminationf(format string, args ...interface{}) { func logTerminationf(format string, args ...interface{}) {
logTermination(fmt.Sprintf(format, args)) logTermination(fmt.Sprintf(format, args...))
} }
func logTermination(args ...interface{}) { func logTermination(args ...interface{}) {
msg := fmt.Sprint(args) msg := fmt.Sprint(args...)
// Write the message to the termination log. This is the default place // Write the message to the termination log. This is not the default place
// that Kubernetes will look for termination information. // that Kubernetes will look for termination information.
log.Debugf("Writing termination message: %v", msg) log.Debugf("Writing termination message: %v", msg)
err := ioutil.WriteFile("/dev/termination-log", []byte(msg), 0660) err := ioutil.WriteFile("/run/termination-log", []byte(msg), 0660)
if err != nil { if err != nil {
log.Debug(err) log.Debug(err)
} }
log.Error(msg) log.Error(msg)
if collectDiagOnFail {
logDiagnostics()
}
} }
func getLogFormat() string { func getLogFormat() string {
@@ -58,7 +67,7 @@ func formatSimple(datetime string, message string) string {
// mirrorSystemErrorLogs starts a goroutine to mirror the contents of the MQ system error logs // mirrorSystemErrorLogs starts a goroutine to mirror the contents of the MQ system error logs
func mirrorSystemErrorLogs(ctx context.Context, wg *sync.WaitGroup, mf mirrorFunc) (chan error, error) { func mirrorSystemErrorLogs(ctx context.Context, wg *sync.WaitGroup, mf mirrorFunc) (chan error, error) {
// Always use the JSON log as the source // Always use the JSON log as the source
return mirrorLog(ctx, wg, "/var/mqm/errors/AMQERR01.json", false, mf) return mirrorLog(ctx, wg, "/var/mqm/errors/AMQERR01.json", false, mf, false)
} }
// mirrorQueueManagerErrorLogs starts a goroutine to mirror the contents of the MQ queue manager error logs // mirrorQueueManagerErrorLogs starts a goroutine to mirror the contents of the MQ queue manager error logs
@@ -70,7 +79,7 @@ func mirrorQueueManagerErrorLogs(ctx context.Context, wg *sync.WaitGroup, name s
return nil, err return nil, err
} }
f := filepath.Join(mqini.GetErrorLogDirectory(qm), "AMQERR01.json") f := filepath.Join(mqini.GetErrorLogDirectory(qm), "AMQERR01.json")
return mirrorLog(ctx, wg, f, fromStart, mf) return mirrorLog(ctx, wg, f, fromStart, mf, true)
} }
func getDebug() bool { func getDebug() bool {
@@ -91,17 +100,35 @@ func configureLogger(name string) (mirrorFunc, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return log.LogDirect, nil return func(msg string, isQMLog bool) bool {
obj, err := processLogMessage(msg)
if err == nil && isQMLog && filterQMLogMessage(obj) {
return false
}
if err != nil {
log.Printf("Failed to unmarshall JSON - %v", msg)
} else {
fmt.Println(msg)
}
return true
}, nil
case "basic": case "basic":
log, err = logger.NewLogger(os.Stderr, d, false, name) log, err = logger.NewLogger(os.Stderr, d, false, name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return func(msg string) { return func(msg string, isQMLog bool) bool {
// Parse the JSON message, and print a simplified version // Parse the JSON message, and print a simplified version
var obj map[string]interface{} obj, err := processLogMessage(msg)
json.Unmarshal([]byte(msg), &obj) if err == nil && isQMLog && filterQMLogMessage(obj) {
fmt.Printf(formatSimple(obj["ibm_datetime"].(string), obj["message"].(string))) return false
}
if err != nil {
log.Printf("Failed to unmarshall JSON - %v", err)
} else {
fmt.Printf(formatSimple(obj["ibm_datetime"].(string), obj["message"].(string)))
}
return true
}, nil }, nil
default: default:
log, err = logger.NewLogger(os.Stdout, d, false, name) log, err = logger.NewLogger(os.Stdout, d, false, name)
@@ -111,3 +138,57 @@ func configureLogger(name string) (mirrorFunc, error) {
return nil, fmt.Errorf("invalid value for LOG_FORMAT: %v", f) return nil, fmt.Errorf("invalid value for LOG_FORMAT: %v", f)
} }
} }
func processLogMessage(msg string) (map[string]interface{}, error) {
var obj map[string]interface{}
err := json.Unmarshal([]byte(msg), &obj)
return obj, err
}
func filterQMLogMessage(obj map[string]interface{}) bool {
hostname, err := os.Hostname()
if os.Getenv("MQ_MULTI_INSTANCE") == "true" && err == nil && !strings.Contains(obj["host"].(string), hostname) {
return true
}
return false
}
func logDiagnostics() {
log.Debug("--- Start Diagnostics ---")
// show the directory ownership/permissions
// #nosec G104
out, _, _ := command.Run("ls", "-l", "/mnt/")
log.Debugf("/mnt/:\n%s", out)
// #nosec G104
out, _, _ = command.Run("ls", "-l", "/mnt/mqm")
log.Debugf("/mnt/mqm:\n%s", out)
// #nosec G104
out, _, _ = command.Run("ls", "-l", "/mnt/mqm/data")
log.Debugf("/mnt/mqm/data:\n%s", out)
// #nosec G104
out, _, _ = command.Run("ls", "-l", "/mnt/mqm-log/log")
log.Debugf("/mnt/mqm-log/log:\n%s", out)
// #nosec G104
out, _, _ = command.Run("ls", "-l", "/mnt/mqm-data/qmgrs")
log.Debugf("/mnt/mqm-data/qmgrs:\n%s", out)
// #nosec G104
out, _, _ = command.Run("ls", "-l", "/var/mqm")
log.Debugf("/var/mqm:\n%s", out)
// #nosec G104
out, _, _ = command.Run("ls", "-l", "/var/mqm/errors")
log.Debugf("/var/mqm/errors:\n%s", out)
// #nosec G104
out, _, _ = command.Run("ls", "-l", "/etc/mqm")
log.Debugf("/etc/mqm:\n%s", out)
// Print out summary of any FDCs
// #nosec G204
cmd := exec.Command("/opt/mqm/bin/ffstsummary")
cmd.Dir = "/var/mqm/errors"
// #nosec G104
outB, _ := cmd.CombinedOutput()
log.Debugf("ffstsummary:\n%s", string(outB))
log.Debug("--- End Diagnostics ---")
}

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2017, 2018 © Copyright IBM Corporation 2017, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -20,21 +20,47 @@ package main
import ( import (
"context" "context"
"errors" "errors"
"flag"
"os" "os"
"sync" "sync"
"github.com/ibm-messaging/mq-container/internal/containerruntimelogger"
"github.com/ibm-messaging/mq-container/internal/metrics" "github.com/ibm-messaging/mq-container/internal/metrics"
"github.com/ibm-messaging/mq-container/internal/name" "github.com/ibm-messaging/mq-container/internal/name"
"github.com/ibm-messaging/mq-container/internal/ready" "github.com/ibm-messaging/mq-container/internal/ready"
"github.com/ibm-messaging/mq-container/internal/tls"
) )
func doMain() error { func doMain() error {
var initFlag = flag.Bool("i", false, "initialize volume only, then exit")
var infoFlag = flag.Bool("info", false, "Display debug info, then exit")
var devFlag = flag.Bool("dev", false, "used when running this program from runmqdevserver to control log output")
flag.Parse()
name, nameErr := name.GetQueueManagerName() name, nameErr := name.GetQueueManagerName()
mf, err := configureLogger(name) mf, err := configureLogger(name)
if err != nil { if err != nil {
logTermination(err) logTermination(err)
return err return err
} }
// Check whether they only want debug info
if *infoFlag {
logVersionInfo()
err = containerruntimelogger.LogContainerDetails(log)
if err != nil {
log.Printf("Error displaying container details: %v", err)
}
return nil
}
err = verifySingleProcess()
if err != nil {
// We don't do the normal termination here as it would create a termination file.
log.Error(err)
return err
}
if nameErr != nil { if nameErr != nil {
logTermination(err) logTermination(err)
return err return err
@@ -58,27 +84,83 @@ func doMain() error {
// Start signal handler // Start signal handler
signalControl := signalHandler(name) signalControl := signalHandler(name)
// Enable diagnostic collecting on failure
collectDiagOnFail = true
err = logConfig() if *devFlag == false {
err = containerruntimelogger.LogContainerDetails(log)
if err != nil {
logTermination(err)
return err
}
}
err = createVolume("/mnt/mqm/data")
if err != nil { if err != nil {
logTermination(err) logTermination(err)
return err return err
} }
err = createVolume("/mnt/mqm") err = createVolume("/mnt/mqm-log/log")
if err != nil { if err != nil {
logTermination(err) logTermination(err)
return err return err
} }
err = createVolume("/mnt/mqm-data/qmgrs")
if err != nil {
logTermination(err)
return err
}
err = createDirStructure() err = createDirStructure()
if err != nil { if err != nil {
logTermination(err) logTermination(err)
return err return err
} }
// handle /var/mqm/ permissions in upgrade to UBI
if *initFlag {
varMqmDirs := []string{
"/var/mqm/config",
"/var/mqm/conv",
"/var/mqm/errors",
"/var/mqm/exits",
"/var/mqm/exits64",
"/var/mqm/log",
"/var/mqm/mqft",
"/var/mqm/qmgrs",
"/var/mqm/shared",
"/var/mqm/sockets",
"/var/mqm/trace",
"/var/mqm/web",
}
err = configureOwnership(varMqmDirs)
if err != nil {
logTermination(err)
return err
}
}
// If init flag is set, exit now
if *initFlag {
return nil
}
// Print out versioning information // Print out versioning information
logVersionInfo() logVersionInfo()
err = postInit(name) keylabel, cmsDB, p12Trust, _, err := tls.ConfigureTLSKeystores(keyDir, trustDir, keyStoreDir)
if err != nil {
logTermination(err)
return err
}
err = configureTLS(keylabel, cmsDB, *devFlag)
if err != nil {
logTermination(err)
return err
}
err = postInit(name, keylabel, p12Trust)
if err != nil { if err != nil {
logTermination(err) logTermination(err)
return err return err
@@ -115,12 +197,18 @@ func doMain() error {
logTermination(err) logTermination(err)
return err return err
} }
err = startQueueManager() err = startQueueManager(name)
if err != nil { if err != nil {
logTermination(err) logTermination(err)
return err return err
} }
configureQueueManager() if standby, _ := ready.IsRunningAsStandbyQM(name); !standby {
err = configureQueueManager()
if err != nil {
logTermination(err)
return err
}
}
enableMetrics := os.Getenv("MQ_ENABLE_METRICS") enableMetrics := os.Getenv("MQ_ENABLE_METRICS")
if enableMetrics == "true" || enableMetrics == "1" { if enableMetrics == "true" || enableMetrics == "1" {
@@ -136,7 +224,11 @@ func doMain() error {
// Reap zombies now, just in case we've already got some // Reap zombies now, just in case we've already got some
signalControl <- reapNow signalControl <- reapNow
// Write a file to indicate that chkmqready should now work as normal // Write a file to indicate that chkmqready should now work as normal
ready.Set() err = ready.Set()
if err != nil {
logTermination(err)
return err
}
// Wait for terminate signal // Wait for terminate signal
<-signalControl <-signalControl
return nil return nil

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -44,22 +44,22 @@ func waitForFile(ctx context.Context, path string) (os.FileInfo, error) {
return nil, fmt.Errorf("mirror: unable to get info on file %v", path) return nil, fmt.Errorf("mirror: unable to get info on file %v", path)
} }
} }
log.Debugf("File exists: %v, %v", path, fi.Size())
return fi, nil return fi, nil
} }
} }
} }
type mirrorFunc func(msg string) type mirrorFunc func(msg string, isQMLog bool) bool
// mirrorAvailableMessages prints lines from the file, until no more are available // mirrorAvailableMessages prints lines from the file, until no more are available
func mirrorAvailableMessages(f *os.File, mf mirrorFunc) { func mirrorAvailableMessages(f *os.File, mf mirrorFunc, isQMLog bool) {
scanner := bufio.NewScanner(f) scanner := bufio.NewScanner(f)
count := 0 count := 0
for scanner.Scan() { for scanner.Scan() {
t := scanner.Text() t := scanner.Text()
mf(t) if mf(t, isQMLog) {
count++ count++
}
} }
if count > 0 { if count > 0 {
log.Debugf("Mirrored %v log entries from %v", count, f.Name()) log.Debugf("Mirrored %v log entries from %v", count, f.Name())
@@ -74,7 +74,7 @@ func mirrorAvailableMessages(f *os.File, mf mirrorFunc) {
// mirrorLog tails the specified file, and logs each line to stdout. // mirrorLog tails the specified file, and logs each line to stdout.
// This is useful for usability, as the container console log can show // This is useful for usability, as the container console log can show
// messages from the MQ error logs. // messages from the MQ error logs.
func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart bool, mf mirrorFunc) (chan error, error) { func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart bool, mf mirrorFunc, isQMLog bool) (chan error, error) {
errorChannel := make(chan error, 1) errorChannel := make(chan error, 1)
var offset int64 = -1 var offset int64 = -1
var f *os.File var f *os.File
@@ -121,6 +121,7 @@ func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart b
if fi == nil { if fi == nil {
return return
} }
log.Debugf("File exists: %v, %v", path, fi.Size())
f, err = os.OpenFile(path, os.O_RDONLY, 0) f, err = os.OpenFile(path, os.O_RDONLY, 0)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
@@ -139,12 +140,15 @@ func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart b
// Always start at the beginning if we've been told to go from the start // Always start at the beginning if we've been told to go from the start
if offset != 0 && !fromStart { if offset != 0 && !fromStart {
log.Debugf("Seeking offset %v in file %v", offset, path) log.Debugf("Seeking offset %v in file %v", offset, path)
f.Seek(offset, 0) _, err = f.Seek(offset, 0)
if err != nil {
log.Errorf("Unable to return to offset %v: %v", offset, err)
}
} }
closing := false closing := false
for { for {
// If there's already data there, mirror it now. // If there's already data there, mirror it now.
mirrorAvailableMessages(f, mf) mirrorAvailableMessages(f, mf, isQMLog)
// Wait for the new log file (after rotation) // Wait for the new log file (after rotation)
newFI, err := waitForFile(ctx, path) newFI, err := waitForFile(ctx, path)
if err != nil { if err != nil {
@@ -158,8 +162,11 @@ func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart b
// log rotation happens before we can open the new file, then we // log rotation happens before we can open the new file, then we
// could skip all those messages. This could happen with a very small // could skip all those messages. This could happen with a very small
// MQ error log size. // MQ error log size.
mirrorAvailableMessages(f, mf) mirrorAvailableMessages(f, mf, isQMLog)
f.Close() err = f.Close()
if err != nil {
log.Errorf("Unable to close mirror file handle: %v", err)
}
// Re-open file // Re-open file
log.Debugf("Re-opening error log file %v", path) log.Debugf("Re-opening error log file %v", path)
f, err = os.OpenFile(path, os.O_RDONLY, 0) f, err = os.OpenFile(path, os.O_RDONLY, 0)
@@ -170,7 +177,7 @@ func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart b
} }
fi = newFI fi = newFI
// Don't seek this time, because we know it's a new file // Don't seek this time, because we know it's a new file
mirrorAvailableMessages(f, mf) mirrorAvailableMessages(f, mf, isQMLog)
} }
select { select {
case <-ctx.Done(): case <-ctx.Done():

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -41,9 +41,10 @@ func TestMirrorLogWithoutRotation(t *testing.T) {
count := 0 count := 0
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup var wg sync.WaitGroup
_, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string) { _, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string, isQMLog bool) bool {
count++ count++
}) return true
}, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -82,9 +83,10 @@ func TestMirrorLogWithRotation(t *testing.T) {
count := 0 count := 0
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup var wg sync.WaitGroup
_, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string) { _, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string, isQMLog bool) bool {
count++ count++
}) return true
}, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -135,9 +137,10 @@ func testMirrorLogExistingFile(t *testing.T, newQM bool) int {
count := 0 count := 0
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup var wg sync.WaitGroup
_, err = mirrorLog(ctx, &wg, tmp.Name(), newQM, func(msg string) { _, err = mirrorLog(ctx, &wg, tmp.Name(), newQM, func(msg string, isQMLog bool) bool {
count++ count++
}) return true
}, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -179,8 +182,9 @@ func TestMirrorLogCancelWhileWaiting(t *testing.T) {
cancel() cancel()
wg.Wait() wg.Wait()
}() }()
_, err := mirrorLog(ctx, &wg, "fake.log", true, func(msg string) { _, err := mirrorLog(ctx, &wg, "fake.log", true, func(msg string, isQMLog bool) bool {
}) return true
}, false)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@@ -1,177 +0,0 @@
/*
© Copyright IBM Corporation 2017, 2018
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 main
import (
"fmt"
"io/ioutil"
"os/user"
"runtime"
"strings"
"github.com/genuinetools/amicontained/container"
)
func logContainerRuntime() error {
r, err := container.DetectRuntime()
if err != nil {
return err
}
log.Printf("Container runtime: %v", r)
return nil
}
func logBaseImage() error {
buf, err := ioutil.ReadFile("/etc/os-release")
if err != nil {
return err
}
lines := strings.Split(string(buf), "\n")
for _, l := range lines {
if strings.HasPrefix(l, "PRETTY_NAME=") {
words := strings.Split(l, "\"")
if len(words) >= 2 {
log.Printf("Base image: %v", words[1])
return nil
}
}
}
return nil
}
func logUser() {
u, err := user.Current()
if err == nil {
g, err := u.GroupIds()
if err != nil {
log.Printf("Running as user ID %v (%v) with primary group %v", u.Uid, u.Name, u.Gid)
} else {
// Look for the primary group in the list of group IDs
for i, v := range g {
if v == u.Gid {
// Remove the element from the slice
g = append(g[:i], g[i+1:]...)
}
}
log.Printf("Running as user ID %v (%v) with primary group %v, and supplemental groups %v", u.Uid, u.Name, u.Gid, strings.Join(g, ","))
}
}
}
// logCapabilities logs the Linux capabilities (e.g. setuid, setgid). See https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
func logCapabilities() error {
caps, err := container.Capabilities()
if err != nil {
return err
}
for k, v := range caps {
if len(v) > 0 {
log.Printf("Capabilities (%s set): %v", strings.ToLower(k), strings.Join(v, ","))
}
}
return nil
}
// logSeccomp logs the seccomp enforcing mode, which affects which kernel calls can be made
func logSeccomp() error {
s, err := container.SeccompEnforcingMode()
if err != nil {
return err
}
log.Printf("seccomp enforcing mode: %v", s)
return nil
}
// logSecurityAttributes logs the security attributes of the current process.
// The security attributes indicate whether AppArmor or SELinux are being used,
// and what the level of confinement is.
func logSecurityAttributes() error {
a, err := readProc("/proc/self/attr/current")
// On some systems, if AppArmor or SELinux are not installed, you get an
// error when you try and read `/proc/self/attr/current`, even though the
// file exists.
if err != nil || a == "" {
a = "none"
}
log.Printf("Process security attributes: %v", a)
return nil
}
func readProc(filename string) (value string, err error) {
buf, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
return strings.TrimSpace(string(buf)), nil
}
func readMounts() error {
all, err := readProc("/proc/mounts")
if err != nil {
log.Print("Error: Couldn't read /proc/mounts")
return err
}
lines := strings.Split(all, "\n")
detected := false
for i := range lines {
parts := strings.Split(lines[i], " ")
//dev := parts[0]
mountPoint := parts[1]
fsType := parts[2]
if strings.Contains(mountPoint, "/mnt") {
log.Printf("Detected '%v' volume mounted to %v", fsType, mountPoint)
detected = true
}
}
if !detected {
log.Print("No volume detected. Persistent messages may be lost")
} else {
return checkFS("/mnt/mqm")
}
return nil
}
func logConfig() error {
log.Printf("CPU architecture: %v", runtime.GOARCH)
if runtime.GOOS == "linux" {
var err error
osr, err := readProc("/proc/sys/kernel/osrelease")
if err != nil {
log.Print(err)
} else {
log.Printf("Linux kernel version: %v", osr)
}
logContainerRuntime()
logBaseImage()
fileMax, err := readProc("/proc/sys/fs/file-max")
if err != nil {
log.Print(err)
} else {
log.Printf("Maximum file handles: %v", fileMax)
}
logUser()
logCapabilities()
logSeccomp()
logSecurityAttributes()
err = readMounts()
if err != nil {
return err
}
} else {
return fmt.Errorf("Unsupported platform: %v", runtime.GOOS)
}
return nil
}

View File

@@ -1,7 +1,5 @@
// +build mqdev
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -19,23 +17,26 @@ package main
import ( import (
"os" "os"
"github.com/ibm-messaging/mq-container/internal/tls"
) )
// postInit is run after /var/mqm is set up // postInit is run after /var/mqm is set up
// This version of postInit is only included as part of the MQ Advanced for Developers build func postInit(name, keylabel string, p12Trust tls.KeyStoreData) error {
func postInit(name string) error { enableWebServer := os.Getenv("MQ_ENABLE_EMBEDDED_WEB_SERVER")
disable := os.Getenv("MQ_DISABLE_WEB_CONSOLE") if enableWebServer == "true" || enableWebServer == "1" {
if disable != "true" && disable != "1" { // Configure the web server (if enabled)
// Configure the web server (if installed) keystore, err := configureWebServer(keylabel, p12Trust)
err := configureWebServer()
if err != nil { if err != nil {
return err return err
} }
// Start the web server, in the background (if installed) // Start the web server, in the background (if installed)
// WARNING: No error handling or health checking available for the web server, // WARNING: No error handling or health checking available for the web server
// which is why it's limited to use with MQ Advanced for Developers only
go func() { go func() {
startWebServer() err = startWebServer(keystore, p12Trust.Password)
if err != nil {
log.Printf("Error starting web server: %v", err)
}
}() }()
} }
return nil return nil

View File

@@ -0,0 +1,64 @@
/*
© Copyright IBM Corporation 2018
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 main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/ibm-messaging/mq-container/internal/command"
)
// Verifies that we are the main or only instance of this program
func verifySingleProcess() error {
programName, err := determineExecutable()
if err != nil {
return fmt.Errorf("Failed to determine name of this program - %v", err)
}
// Verify that there is only one runmqserver
_, err = verifyOnlyOne(programName)
if err != nil {
return fmt.Errorf("You cannot run more than one instance of this program")
}
return nil
}
// Verifies that there is only one instance running of the given program name.
func verifyOnlyOne(programName string) (int, error) {
// #nosec G104
out, _, _ := command.Run("ps", "-e", "--format", "cmd")
//if this goes wrong then assume we are the only one
numOfProg := strings.Count(out, programName)
if numOfProg != 1 {
return numOfProg, fmt.Errorf("Expected there to be only 1 instance of %s but found %d", programName, numOfProg)
}
return numOfProg, nil
}
// Determines the name of the currently running executable.
func determineExecutable() (string, error) {
file, err := os.Executable()
if err != nil {
return "", err
}
_, exec := filepath.Split(file)
return exec, nil
}

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2017, 2018 © Copyright IBM Corporation 2017, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -16,14 +16,20 @@ limitations under the License.
package main package main
import ( import (
"io" "bytes"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"golang.org/x/sys/unix"
"github.com/ibm-messaging/mq-container/internal/command" "github.com/ibm-messaging/mq-container/internal/command"
containerruntime "github.com/ibm-messaging/mq-container/internal/containerruntime"
"github.com/ibm-messaging/mq-container/internal/mqscredact"
"github.com/ibm-messaging/mq-container/internal/ready"
) )
// createDirStructure creates the default MQ directory structure under /var/mqm // createDirStructure creates the default MQ directory structure under /var/mqm
@@ -37,21 +43,90 @@ func createDirStructure() error {
return nil return nil
} }
// configureOwnership recursively handles ownership of files within the given filepath
func configureOwnership(paths []string) error {
uid, gid, err := command.LookupMQM()
if err != nil {
return err
}
var fileInfo *unix.Stat_t
fileInfo = new(unix.Stat_t)
for _, root := range paths {
_, err = os.Stat(root)
if err != nil {
if os.IsNotExist(err) {
continue
}
return err
}
err = filepath.Walk(root, func(from string, info os.FileInfo, err error) error {
if err != nil {
return err
}
to := fmt.Sprintf("%v%v", root, from[len(root):])
err = unix.Stat(to, fileInfo)
if err != nil {
return err
}
fileUID := fmt.Sprint(fileInfo.Uid)
if strings.Compare(fileUID, "999") == 0 {
err = os.Chown(to, uid, gid)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
}
return nil
}
// createQueueManager creates a queue manager, if it doesn't already exist. // createQueueManager creates a queue manager, if it doesn't already exist.
// It returns true if one was created, or false if one already existed // It returns true if one was created (or a standby was created), or false if one already existed
func createQueueManager(name string) (bool, error) { func createQueueManager(name string) (bool, error) {
log.Printf("Creating queue manager %v", name) log.Printf("Creating queue manager %v", name)
out, rc, err := command.Run("crtmqm", "-q", "-p", "1414", name)
// Run 'dspmqinf' to check if 'mqs.ini' configuration file exists
// If command succeeds, the queue manager (or standby queue manager) has already been created
_, _, err := command.Run("dspmqinf", name)
if err == nil {
log.Printf("Detected existing queue manager %v", name)
return false, nil
}
mounts, err := containerruntime.GetMounts()
if err != nil { if err != nil {
// 8=Queue manager exists, which is fine log.Printf("Error getting mounts for queue manager")
if rc == 8 {
log.Printf("Detected existing queue manager %v", name)
return false, nil
}
log.Printf("crtmqm returned %v", rc)
log.Println(string(out))
return false, err return false, err
} }
// Check if 'qm.ini' configuration file exists for the queue manager
// TODO : handle possible race condition - use a file lock?
dataDir := getQueueManagerDataDir(mounts, name)
_, err = os.Stat(filepath.Join(dataDir, "qm.ini"))
if err != nil {
// If 'qm.ini' is not found - run 'crtmqm' to create a new queue manager
args := getCreateQueueManagerArgs(mounts, name)
out, rc, err := command.Run("crtmqm", args...)
if err != nil {
log.Printf("Error %v creating queue manager: %v", rc, string(out))
return false, err
}
} else {
// If 'qm.ini' is found - run 'addmqinf' to create a standby queue manager with existing configuration
args := getCreateStandbyQueueManagerArgs(name)
out, rc, err := command.Run("addmqinf", args...)
if err != nil {
log.Printf("Error %v creating standby queue manager: %v", rc, string(out))
return false, err
}
log.Println("Created standby queue manager")
return true, nil
}
log.Println("Created queue manager")
return true, nil return true, nil
} }
@@ -68,10 +143,15 @@ func updateCommandLevel() error {
return nil return nil
} }
func startQueueManager() error { func startQueueManager(name string) error {
log.Println("Starting queue manager") log.Println("Starting queue manager")
out, rc, err := command.Run("strmqm") out, rc, err := command.Run("strmqm", "-x", name)
if err != nil { if err != nil {
// 30=standby queue manager started, which is fine
if rc == 30 {
log.Printf("Started standby queue manager")
return nil
}
log.Printf("Error %v starting queue manager: %v", rc, string(out)) log.Printf("Error %v starting queue manager: %v", rc, string(out))
return err return err
} }
@@ -86,35 +166,49 @@ func configureQueueManager() error {
log.Println(err) log.Println(err)
return err return err
} }
for _, file := range files { for _, file := range files {
if strings.HasSuffix(file.Name(), ".mqsc") { if strings.HasSuffix(file.Name(), ".mqsc") {
abs := filepath.Join(configDir, file.Name()) abs := filepath.Join(configDir, file.Name())
// #nosec G204
verify := exec.Command("runmqsc", "-v", "-e")
// #nosec G204 - command is fixed, no injection vector
cmd := exec.Command("runmqsc") cmd := exec.Command("runmqsc")
stdin, err := cmd.StdinPipe() // Read mqsc file into variable
// #nosec G304 - filename variable is derived from contents of 'configDir' which is a defined constant
mqsc, err := ioutil.ReadFile(abs)
if err != nil { if err != nil {
log.Println(err) log.Printf("Error reading file %v: %v", abs, err)
return err continue
} }
// Open the MQSC file for reading // Write mqsc to buffer
f, err := os.Open(abs) var buffer bytes.Buffer
_, err = buffer.Write(mqsc)
if err != nil { if err != nil {
log.Printf("Error opening %v: %v", abs, err) log.Printf("Error writing MQSC file %v to buffer: %v", abs, err)
continue
} }
// Copy the contents to stdin of the runmqsc process verifyBuffer := buffer
_, err = io.Copy(stdin, f)
// Buffer mqsc to stdin of runmqsc
cmd.Stdin = &buffer
verify.Stdin = &verifyBuffer
// Verify the MQSC commands
out, err := verify.CombinedOutput()
if err != nil { if err != nil {
log.Printf("Error reading %v: %v", abs, err) log.Errorf("Error verifying MQSC file %v (%v):\n\t%v", file.Name(), err, formatMQSCOutput(string(out)))
return fmt.Errorf("Error verifying MQSC file %v (%v):\n\t%v", file.Name(), err, formatMQSCOutput(string(out)))
} }
f.Close()
stdin.Close() // Run runmqsc command
// Run the command and wait for completion out, err = cmd.CombinedOutput()
out, err := cmd.CombinedOutput()
if err != nil { if err != nil {
log.Println(err) log.Errorf("Error running MQSC file %v (%v):\n\t%v", file.Name(), err, formatMQSCOutput(string(out)))
continue
} else {
// Print the runmqsc output, adding tab characters to make it more readable as part of the log
log.Printf("Output for \"runmqsc\" with %v:\n\t%v", abs, formatMQSCOutput(string(out)))
} }
// Print the runmqsc output, adding tab characters to make it more readable as part of the log
log.Printf("Output for \"runmqsc\" with %v:\n\t%v", abs, strings.Replace(string(out), "\n", "\n\t", -1))
} }
} }
return nil return nil
@@ -122,11 +216,77 @@ func configureQueueManager() error {
func stopQueueManager(name string) error { func stopQueueManager(name string) error {
log.Println("Stopping queue manager") log.Println("Stopping queue manager")
out, _, err := command.Run("endmqm", "-w", name) isStandby, err := ready.IsRunningAsStandbyQM(name)
if err != nil { if err != nil {
log.Printf("Error stopping queue manager: %v", string(out)) log.Printf("Error getting status for queue manager %v: ", name, err.Error())
return err return err
} }
log.Println("Stopped queue manager") args := []string{"-w", "-r", name}
if os.Getenv("MQ_MULTI_INSTANCE") == "true" {
if isStandby {
args = []string{"-x", name}
} else {
args = []string{"-s", "-w", "-r", name}
}
}
out, rc, err := command.Run("endmqm", args...)
if err != nil {
log.Printf("Error %v stopping queue manager: %v", rc, string(out))
return err
}
if isStandby {
log.Printf("Stopped standby queue manager")
} else {
log.Println("Stopped queue manager")
}
return nil return nil
} }
func formatMQSCOutput(out string) string {
// redact sensitive information
out, _ = mqscredact.Redact(out)
// add tab characters to make it more readable as part of the log
return strings.Replace(string(out), "\n", "\n\t", -1)
}
func isStandbyQueueManager(name string) (bool, error) {
out, rc, err := command.Run("dspmq", "-n", "-m", name)
if err != nil {
log.Printf("Error %v getting status for queue manager %v: %v", rc, name, string(out))
return false, err
}
if strings.Contains(string(out), "(RUNNING AS STANDBY)") {
return true, nil
}
return false, nil
}
func getQueueManagerDataDir(mounts map[string]string, name string) string {
dataDir := filepath.Join("/var/mqm/qmgrs", name)
if _, ok := mounts["/mnt/mqm-data"]; ok {
dataDir = filepath.Join("/mnt/mqm-data/qmgrs", name)
}
return dataDir
}
func getCreateQueueManagerArgs(mounts map[string]string, name string) []string {
args := []string{"-q", "-p", "1414"}
if _, ok := mounts["/mnt/mqm-log"]; ok {
args = append(args, "-ld", "/mnt/mqm-log/log")
}
if _, ok := mounts["/mnt/mqm-data"]; ok {
args = append(args, "-md", "/mnt/mqm-data/qmgrs")
}
args = append(args, name)
return args
}
func getCreateStandbyQueueManagerArgs(name string) []string {
args := []string{"-s", "QueueManager"}
args = append(args, "-v", fmt.Sprintf("Name=%v", name))
args = append(args, "-v", fmt.Sprintf("Directory=%v", name))
args = append(args, "-v", "Prefix=/var/mqm")
args = append(args, "-v", fmt.Sprintf("DataPath=/mnt/mqm-data/qmgrs/%v", name))
return args
}

View File

@@ -43,7 +43,8 @@ func signalHandler(qmgr string) chan int {
log.Printf("Signal received: %v", sig) log.Printf("Signal received: %v", sig)
signal.Stop(reapSignals) signal.Stop(reapSignals)
signal.Stop(stopSignals) signal.Stop(stopSignals)
metrics.StopMetricsGathering() metrics.StopMetricsGathering(log)
// #nosec G104
stopQueueManager(qmgr) stopQueueManager(qmgr)
// One final reap // One final reap
reapZombies() reapZombies()

143
cmd/runmqserver/tls.go Normal file
View File

@@ -0,0 +1,143 @@
/*
© Copyright IBM Corporation 2018, 2019
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 main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/ibm-messaging/mq-container/internal/command"
"github.com/ibm-messaging/mq-container/internal/mqtemplate"
"github.com/ibm-messaging/mq-container/internal/tls"
)
// Location to store the keystores
const keyStoreDir = "/run/runmqserver/tls/"
// KeyDir is the location of the certificate keys to import
const keyDir = "/etc/mqm/pki/keys"
// TrustDir is the location of the Certifates to add
const trustDir = "/etc/mqm/pki/trust"
// configureWebTLS configures TLS for Web Console
func configureWebTLS(label string) error {
// Return immediately if we have no certificate to use as identity
if label == "" {
return nil
}
webConfigDir := "/etc/mqm/web/installations/Installation1/servers/mqweb"
tls := "tls.xml"
tlsConfig := filepath.Join(webConfigDir, tls)
newTLSConfig := filepath.Join(webConfigDir, tls+".tpl")
err := os.Remove(tlsConfig)
if err != nil {
return fmt.Errorf("Could not delete file %s: %v", tlsConfig, err)
}
// we symlink here to prevent issues on restart
err = os.Symlink(newTLSConfig, tlsConfig)
if err != nil {
return fmt.Errorf("Could not create symlink %s->%s: %v", newTLSConfig, tlsConfig, err)
}
mqmUID, mqmGID, err := command.LookupMQM()
if err != nil {
return fmt.Errorf("Could not find mqm user or group: %v", err)
}
err = os.Chown(tlsConfig, mqmUID, mqmGID)
if err != nil {
return fmt.Errorf("Could change ownership of %s to mqm: %v", tlsConfig, err)
}
return nil
}
// configureTLSDev configures TLS for developer defaults
func configureTLSDev() error {
const mqsc string = "/etc/mqm/20-dev-tls.mqsc"
const mqscTemplate string = mqsc + ".tpl"
const sslCipherSpec string = "TLS_RSA_WITH_AES_128_CBC_SHA256"
if os.Getenv("MQ_DEV") == "true" {
err := mqtemplate.ProcessTemplateFile(mqscTemplate, mqsc, map[string]string{
"SSLCipherSpec": sslCipherSpec,
}, log)
if err != nil {
return err
}
} else {
_, err := os.Stat(mqsc)
if !os.IsNotExist(err) {
err = os.Remove(mqsc)
if err != nil {
log.Errorf("Error removing file %s: %v", mqsc, err)
return err
}
}
}
return nil
}
// configureTLS configures TLS for queue manager
func configureTLS(certLabel string, cmsKeystore tls.KeyStoreData, devmode bool) error {
log.Debug("Configuring TLS")
const mqsc string = "/etc/mqm/15-tls.mqsc"
const mqscTemplate string = mqsc + ".tpl"
err := mqtemplate.ProcessTemplateFile(mqscTemplate, mqsc, map[string]string{
"SSLKeyR": strings.TrimSuffix(cmsKeystore.Keystore.Filename, ".kdb"),
"CertificateLabel": certLabel,
}, log)
if err != nil {
return err
}
if devmode && certLabel != "" {
err = configureTLSDev()
if err != nil {
return err
}
}
return nil
}
// configureSSOTLS configures MQ Console TLS for Single Sign-On
func configureSSOTLS(p12TrustStore tls.KeyStoreData) (string, error) {
// TODO find way to supply this
// Override the webstore variables to hard coded defaults
webKeyStoreName := tls.IntegrationDefaultLabel + ".p12"
// Check keystore exists
ks := filepath.Join(keyStoreDir, webKeyStoreName)
_, err := os.Stat(ks)
if err != nil {
return "", fmt.Errorf("Failed to find existing keystore %s: %v", ks, err)
}
// Check truststore exists
_, err = os.Stat(p12TrustStore.Keystore.Filename)
if err != nil {
return "", fmt.Errorf("Failed to find existing truststore %s: %v", p12TrustStore.Keystore.Filename, err)
}
return webKeyStoreName, nil
}

View File

@@ -23,9 +23,14 @@ import (
) )
var ( var (
ImageCreated = "Not specified" // ImageCreated is the date the image was built
ImageCreated = "Not specified"
// ImageRevision is the source control revision identifier
ImageRevision = "Not specified" ImageRevision = "Not specified"
ImageSource = "Not specified" // ImageSource is the URL to get source code for building the image
ImageSource = "Not specified"
// ImageTag is the tag of the image
ImageTag = "Not specified"
) )
func logDateStamp() { func logDateStamp() {
@@ -40,6 +45,10 @@ func logGitCommit() {
log.Printf("Image source: %v", ImageSource) log.Printf("Image source: %v", ImageSource)
} }
func logImageTag() {
log.Printf("Image tag: %v", ImageTag)
}
func logMQVersion() { func logMQVersion() {
mqVersion, _, err := command.Run("dspmqver", "-b", "-f", "2") mqVersion, _, err := command.Run("dspmqver", "-b", "-f", "2")
if err != nil { if err != nil {
@@ -64,5 +73,6 @@ func logVersionInfo() {
logDateStamp() logDateStamp()
logGitRepo() logGitRepo()
logGitCommit() logGitCommit()
logImageTag()
logMQVersion() logMQVersion()
} }

View File

@@ -1,7 +1,5 @@
// +build mqdev
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -19,21 +17,62 @@ package main
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"os/exec"
"os/user"
"path/filepath" "path/filepath"
"strconv"
"strings"
"syscall"
"github.com/ibm-messaging/mq-container/internal/command" "github.com/ibm-messaging/mq-container/internal/command"
"github.com/ibm-messaging/mq-container/internal/copy"
"github.com/ibm-messaging/mq-container/internal/mqtemplate"
"github.com/ibm-messaging/mq-container/internal/tls"
) )
func startWebServer() error { func startWebServer(keystore, keystorepw string) error {
_, err := os.Stat("/opt/mqm/bin/strmqweb") _, err := os.Stat("/opt/mqm/bin/strmqweb")
if err != nil && os.IsNotExist(err) { if err != nil && os.IsNotExist(err) {
log.Debug("Skipping web server, because it's not installed") log.Debug("Skipping web server, because it's not installed")
return nil return nil
} }
log.Println("Starting web server") log.Println("Starting web server")
out, rc, err := command.RunAsMQM("strmqweb") // #nosec G204 - command is fixed, no injection vector
cmd := exec.Command("strmqweb")
// Set a default app password for the web server, if one isn't already set
_, set := os.LookupEnv("MQ_APP_PASSWORD")
if !set {
// Take all current environment variables, and add the app password
cmd.Env = append(os.Environ(), "MQ_APP_PASSWORD=passw0rd")
} else {
cmd.Env = os.Environ()
}
// TLS enabled
if keystore != "" {
cmd.Env = append(cmd.Env, "AMQ_WEBKEYSTORE="+keystore)
cmd.Env = append(cmd.Env, "AMQ_WEBKEYSTOREPW="+keystorepw)
}
uid, gid, err := command.LookupMQM()
if err != nil {
return err
}
u, err := user.Current()
if err != nil {
return err
}
currentUID, err := strconv.Atoi(u.Uid)
if err != nil {
return fmt.Errorf("Error converting UID to string: %v", err)
}
// Add credentials to run as 'mqm', only if we aren't already 'mqm'
if currentUID != uid {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
out, rc, err := command.RunCmd(cmd)
if err != nil { if err != nil {
log.Printf("Error %v starting web server: %v", rc, string(out)) log.Printf("Error %v starting web server: %v", rc, string(out))
return err return err
@@ -42,45 +81,83 @@ func startWebServer() error {
return nil return nil
} }
// CopyFile copies the specified file func configureSSO(p12TrustStore tls.KeyStoreData) (string, error) {
func CopyFile(src, dest string) error { // Ensure all required environment variables are set for SSO
log.Debugf("Copying file %v to %v", src, dest) requiredEnvVars := []string{
in, err := os.Open(src) "MQ_WEB_ADMIN_USERS",
if err != nil { "MQ_OIDC_CLIENT_ID",
return err "MQ_OIDC_CLIENT_SECRET",
"MQ_OIDC_UNIQUE_USER_IDENTIFIER",
"MQ_OIDC_AUTHORIZATION_ENDPOINT",
"MQ_OIDC_TOKEN_ENDPOINT",
"MQ_OIDC_JWK_ENDPOINT",
"MQ_OIDC_ISSUER_IDENTIFIER",
"MQ_OIDC_CERTIFICATE",
} }
defer in.Close() for _, envVar := range requiredEnvVars {
if len(os.Getenv(envVar)) == 0 {
out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, 0770) return "", fmt.Errorf("%v must be set when MQ_BETA_ENABLE_SSO=true", envVar)
defer out.Close() }
_, err = io.Copy(out, in)
if err != nil {
return err
} }
err = out.Close()
return err
}
func configureWebServer() error { // Check mqweb directory exists
_, err := os.Stat("/opt/mqm/bin/strmqweb") const mqwebDir string = "/etc/mqm/web/installations/Installation1/servers/mqweb"
_, err := os.Stat(mqwebDir)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil return "", nil
} }
return err return "", err
}
// Process SSO template for generating file mqwebuser.xml
adminUsers := strings.Split(os.Getenv("MQ_WEB_ADMIN_USERS"), "\n")
err = mqtemplate.ProcessTemplateFile(mqwebDir+"/mqwebuser.xml.tpl", mqwebDir+"/mqwebuser.xml", map[string][]string{"AdminUser": adminUsers}, log)
if err != nil {
return "", err
}
// Configure SSO TLS
return configureSSOTLS(p12TrustStore)
}
func configureWebServer(keyLabel string, p12Trust tls.KeyStoreData) (string, error) {
var keystore string
// Configure TLS for Web Console first if we have a certificate to use
err := configureWebTLS(keyLabel)
if err != nil {
return keystore, err
}
if keyLabel != "" {
keystore = keyLabel + ".p12"
}
// Configure Single-Sign-On for the web server (if enabled)
enableSSO := os.Getenv("MQ_BETA_ENABLE_SSO")
if enableSSO == "true" || enableSSO == "1" {
keystore, err = configureSSO(p12Trust)
if err != nil {
return keystore, err
}
}
_, err = os.Stat("/opt/mqm/bin/strmqweb")
if err != nil {
if os.IsNotExist(err) {
return keystore, nil
}
return keystore, err
} }
const webConfigDir string = "/etc/mqm/web" const webConfigDir string = "/etc/mqm/web"
_, err = os.Stat(webConfigDir) _, err = os.Stat(webConfigDir)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil return keystore, nil
} }
return err return keystore, err
} }
uid, gid, err := command.LookupMQM() uid, gid, err := command.LookupMQM()
if err != nil { if err != nil {
return err return keystore, err
} }
const prefix string = "/etc/mqm/web" const prefix string = "/etc/mqm/web"
err = filepath.Walk(prefix, func(from string, info os.FileInfo, err error) error { err = filepath.Walk(prefix, func(from string, info os.FileInfo, err error) error {
@@ -99,6 +176,7 @@ func configureWebServer() error {
} }
if info.IsDir() { if info.IsDir() {
if !exists { if !exists {
// #nosec G301 - write group permissions are required
err := os.MkdirAll(to, 0770) err := os.MkdirAll(to, 0770)
if err != nil { if err != nil {
return err return err
@@ -111,7 +189,7 @@ func configureWebServer() error {
return err return err
} }
} }
err := CopyFile(from, to) err := copy.CopyFile(from, to)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
return err return err
@@ -123,5 +201,5 @@ func configureWebServer() error {
} }
return nil return nil
}) })
return err return keystore, err
} }

View File

@@ -1,25 +1,28 @@
# Building a Docker image # Building a container image
## Prerequisites ## Prerequisites
You need to ensure you have the following tools installed:
You need to have the following tools installed:
* [Docker](https://www.docker.com/) V17.06.1 or later * [Docker](https://www.docker.com/) V17.06.1 or later
* [GNU make](https://www.gnu.org/software/make/) * [GNU make](https://www.gnu.org/software/make/)
If you are working in the Windows Subsystem for Linux, follow [this guide by Microsoft to set up Docker](https://blogs.msdn.microsoft.com/commandline/2017/12/08/cross-post-wsl-interoperability-with-docker/) first. If you are working in the Windows Subsystem for Linux, follow [this guide by Microsoft to set up Docker](https://blogs.msdn.microsoft.com/commandline/2017/12/08/cross-post-wsl-interoperability-with-docker/) first.
## Building a production image ## Building a production image
This procedure works for building the MQ Continuous Delivery release, on `x86_64`, `ppc64le` and `s390x` architectures.
This procedure works for building the MQ Continuous Delivery release, on `amd64`, `ppc64le` and `s390x` architectures.
1. Create a `downloads` directory in the root of this repository 1. Create a `downloads` directory in the root of this repository
2. Download MQ from IBM Passport Advantage, and place the downloaded file (for example, `IBM_MQ_9.1.0.0_UBUNTU_X86-64.tar.gz` for MQ V9.1.0 for Ubuntu on x86_64 architecture) in the `downloads` directory 2. Download MQ from [IBM Passport Advantage](https://www.ibm.com/software/passportadvantage/) or [IBM Fix Central](https://www.ibm.com/support/fixcentral), and place the downloaded file (for example, `IBM_MQ_9.1.2_UBUNTU_X86-64.tar.gz`) in the `downloads` directory
2. Run `make build-advancedserver` 3. Run `make build-advancedserver`
> **Warning**: Note that MQ offers two different sets of packaging on Linux: one is called "MQ for Linux" and contains RPM files for installing on Red Hat Enterprise Linux and SUSE Linux Enterprise Server. The other package is called "MQ for Ubuntu", and contains DEB files for installing on Ubuntu. > **Warning**: Note that MQ offers two different sets of packaging on Linux: one is called "MQ for Linux" and contains RPM files for installing on Red Hat Enterprise Linux and SUSE Linux Enterprise Server. The MQ container build uses a Red Hat Universal Base Image, so you need the "MQ for Linux" RPM files.
You can build a different version of MQ by setting the `MQ_VERSION` environment variable, for example: You can build a different version of MQ by setting the `MQ_VERSION` environment variable, for example:
```bash ```bash
MQ_VERSION=9.0.5.0 make build-advancedserver MQ_VERSION=9.1.0.0 make build-advancedserver
``` ```
If you have an MQ archive file with a different file name, you can specify a particular file (which must be in the `downloads` directory). You should also specify the MQ version, so that the resulting image is tagged correctly, for example: If you have an MQ archive file with a different file name, you can specify a particular file (which must be in the `downloads` directory). You should also specify the MQ version, so that the resulting image is tagged correctly, for example:
@@ -29,22 +32,15 @@ MQ_ARCHIVE=mq-1.2.3.4.tar.gz MQ_VERSION=1.2.3.4 make build-advancedserver
``` ```
## Building a developer image ## Building a developer image
Run `make build-devserver`, which will download the latest version of MQ Advanced for Developers from IBM developerWorks. This is currently only available on the `x86_64` architecture. Run `make build-devserver`, which will download the latest version of MQ Advanced for Developers from IBM developerWorks. This is currently only available on the `amd64` architecture.
You can use the environment variable `MQ_ARCHIVE_DEV` to specify an alternative local file to install from (which must be in the `downloads` directory). You can use the environment variable `MQ_ARCHIVE_DEV` to specify an alternative local file to install from (which must be in the `downloads` directory).
## Building on a different base image ## Building from a Red Hat Enterprise Linux host
By default, the MQ images use Ubuntu as the base layer. You can build using a Red Hat Enterprise Linux compatible base layer by setting the `BASE_IMAGE` environment variable. For example: Red Hat Enterprise Linux (RHEL) offers a suite of container tools, including Buildah for building container images, and Podman for running containers. Buildah can accept input described in a [Dockerfile](https://docs.docker.com/engine/reference/builder/). This MQ sample uses a multi-stage build, which requires a recent version of Podman, which is not yet available in Red Hat Enterprise Linux V7. Therefore, if you are on a RHEL host, then the `build-devserver` and `build-advancedserver` targets are run using a more recent version of Buildah from inside a container.
```
BASE_IMAGE=centos:7 make build-advancedserver
```
The `make` tool will try and locate the right archive file under the `downloads` directory, based on your platform architecture and your `MQ_VERSION` environment variable, for example `IBM_MQ_9.1.0.0_LINUX_X86_64.tar.gz` for MQ V9.1.0.0 on x86_64. You can also set the `MQ_ARCHIVE` environment variable to set the specific file name.
Note that if you are using Red Hat Enterprise Linux, you will need to create your own base image layer, with your subscription enabled, as described [here](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_atomic_host/7/html/getting_started_with_containers/get_started_with_docker_formatted_container_images). The MQ image build needs to install some additional packages, and a subscription is required to access the Red Hat repositories.
The containerized build process on a RHEL host will write an OCI compliant archive file to `/tmp/mq-buildah`. If a version of Docker is installed on the host, it will also push the image into Docker's internal image registry.
## Installed components ## Installed components
This image includes the core MQ server, Java, language packs, and GSKit. This can be configured by setting the `MQ_PACKAGES` argument to `make`, or directly as a [Docker build argument](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables-build-arg). This image includes the core MQ server, Java, language packs, GSKit, and web server. This can be configured by setting the `MQ_PACKAGES` argument to `make`.

View File

@@ -9,8 +9,8 @@ The MQ Developer Defaults supports some customization options, these are all con
* **MQ_DEV** - Set this to `false` to stop the default objects being created. * **MQ_DEV** - Set this to `false` to stop the default objects being created.
* **MQ_ADMIN_PASSWORD** - Changes the password of the `admin` user. Must be at least 8 characters long. * **MQ_ADMIN_PASSWORD** - Changes the password of the `admin` user. Must be at least 8 characters long.
* **MQ_APP_PASSWORD** - Changes the password of the app user. If set, this will cause the `DEV.APP.SVRCONN` channel to become secured and only allow connections that supply a valid userid and password. Must be at least 8 characters long. * **MQ_APP_PASSWORD** - Changes the password of the app user. If set, this will cause the `DEV.APP.SVRCONN` channel to become secured and only allow connections that supply a valid userid and password. Must be at least 8 characters long.
* **MQ_TLS_KEYSTORE** - Allows you to supply the location of a PKCS#12 keystore containing a single certificate which you want to use in both the web console and the queue manager. Requires `MQ_TLS_PASSPHRASE`. When enabled the channels created will be secured using the `TLS_RSA_WITH_AES_128_CBC_SHA256` CipherSpec. *Note*: you will need to make the keystore available inside your container, this can be done by mounting a volume to your container. * **MQ_TLS_KEYSTORE** - **DEPRECATED**. See section `Supplying TLS certificates` in [usage document](usage.md). Allows you to supply the location of a PKCS#12 keystore containing a single certificate which you want to use in both the web console and the queue manager. Requires `MQ_TLS_PASSPHRASE`. When enabled the channels created will be secured using the `TLS_RSA_WITH_AES_128_CBC_SHA256` CipherSpec. *Note*: you will need to make the keystore available inside your container, this can be done by mounting a volume to your container.
* **MQ_TLS_PASSPHRASE** - Passphrase for the keystore referenced in `MQ_TLS_KEYSTORE`. * **MQ_TLS_PASSPHRASE** - **DEPRECATED**. See section `Supplying TLS certificates` in [usage document](usage.md). Passphrase for the keystore referenced in `MQ_TLS_KEYSTORE`.
## Details of the default configuration ## Details of the default configuration
@@ -52,4 +52,4 @@ If you choose to accept the security warning, you will be presented with the log
If you wish to change the password for the admin user, this can be done using the `MQ_ADMIN_PASSWORD` environment variable. If you supply a PKCS#12 keystore using the `MQ_TLS_KEYSTORE` environment variable, then the web console will be configured to use the certificate inside the keystore for HTTPS operations. If you wish to change the password for the admin user, this can be done using the `MQ_ADMIN_PASSWORD` environment variable. If you supply a PKCS#12 keystore using the `MQ_TLS_KEYSTORE` environment variable, then the web console will be configured to use the certificate inside the keystore for HTTPS operations.
If you do not wish the web console to run, you can disable it by setting the environment variable `MQ_DISABLE_WEB_CONSOLE` to `true`. If you do not wish the web console to run, you can disable it by setting the environment variable `MQ_ENABLE_EMBEDDED_WEB_SERVER` to `false`.

View File

@@ -24,6 +24,7 @@ The `runmqserver` command has the following responsibilities:
- Works as PID 1, so is responsible for [reaping zombie processes](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/) - Works as PID 1, so is responsible for [reaping zombie processes](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/)
* Creating and starting a queue manager * Creating and starting a queue manager
* Configuring the queue manager, by running any MQSC scripts found under `/etc/mqm` * Configuring the queue manager, by running any MQSC scripts found under `/etc/mqm`
* Starts the MQ web server (if enabled)
* Starting Prometheus metrics generation for the queue manager (if enabled) * Starting Prometheus metrics generation for the queue manager (if enabled)
* Indicates to the `chkmqready` command that configuration is complete, and that normal readiness checking can happen. This is done by writing a file into `/run/runmqserver` * Indicates to the `chkmqready` command that configuration is complete, and that normal readiness checking can happen. This is done by writing a file into `/run/runmqserver`
@@ -36,8 +37,6 @@ The `runmqdevserver` command is added to the MQ Advanced for Developers image on
2. Generates MQSC files to put in `/etc/mqm`, based on a template, which is updated with values based on supplied environment variables. 2. Generates MQSC files to put in `/etc/mqm`, based on a template, which is updated with values based on supplied environment variables.
3. If requested, it creates TLS key stores under `/run/runmqdevserver`, and configures MQ and the web server to use them 3. If requested, it creates TLS key stores under `/run/runmqdevserver`, and configures MQ and the web server to use them
A special version of `runmqserver` is used in the developer image, which performs extra actions like starting the web server. This is built using the `mqdev` [build constraint](https://golang.org/pkg/go/build/#hdr-Build_Constraints).
## Prometheus metrics ## Prometheus metrics
[Prometheus](https://prometheus.io) metrics are generated for the queue manager as follows: [Prometheus](https://prometheus.io) metrics are generated for the queue manager as follows:

39
docs/security.md Normal file
View File

@@ -0,0 +1,39 @@
# Security
## Container runtime
### User
The MQ server image is run using the "mqm" user, with a fixed UID and GID of 888.
### Capabilities
The MQ Advanced image requires no Linux capabilities, so you can drop any capabilities which are added by default. For example, in Docker you could do the following:
```sh
docker run \
--cap-drop=ALL \
--env LICENSE=accept \
--env MQ_QMGR_NAME=QM1 \
--detach \
mqadvanced-server:9.1.2.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 \
mqadvanced-server-dev:9.1.2.0-amd64
```
### SELinux
The SELinux label "spc_t" (super-privileged container) is needed to run the MQ container on a host with SELinux enabled. This is due to a current limitation in how MQ data is stored on volumes, which violates the usual policy applied when using the standard "container_t" label.

View File

@@ -6,7 +6,6 @@ You need to ensure you have the following tools installed:
* [GNU make](https://www.gnu.org/software/make/) * [GNU make](https://www.gnu.org/software/make/)
* [Go](https://golang.org/) - only needed for running the tests * [Go](https://golang.org/) - only needed for running the tests
* [dep](https://github.com/golang/dep) (official Go dependency management tool) - needed to prepare for running the tests * [dep](https://github.com/golang/dep) (official Go dependency management tool) - needed to prepare for running the tests
* [Helm](https://helm.sh) - only needed for running the Kubernetes tests
## Running the tests ## Running the tests
There are two main sets of tests: There are two main sets of tests:
@@ -25,7 +24,7 @@ make test-advancedserver
You can specify the image to use directly by using the `MQ_IMAGE_ADVANCEDSERVER` or `MQ_IMAGE_DEVSERVER` variables, for example: You can specify the image to use directly by using the `MQ_IMAGE_ADVANCEDSERVER` or `MQ_IMAGE_DEVSERVER` variables, for example:
``` ```
MQ_IMAGE_ADVANCEDSERVER=mqadvanced-server:9.1.0.0-x86_64-ubuntu-16.04 make test-advancedserver MQ_IMAGE_ADVANCEDSERVER=mqadvanced-server:9.1.2.0-amd64 make test-advancedserver
``` ```
You can pass parameters to `go test` with an environment variable. For example, to run the "TestGoldenPath" test, run the following command:: You can pass parameters to `go test` with an environment variable. For example, to run the "TestGoldenPath" test, run the following command::
@@ -34,10 +33,10 @@ You can pass parameters to `go test` with an environment variable. For example,
TEST_OPTS_DOCKER="-run TestGoldenPath" make test-advancedserver TEST_OPTS_DOCKER="-run TestGoldenPath" make test-advancedserver
``` ```
You can also use the same environment variables you specified when [building](./building), for example, the following will try and test an image called `mqadvanced-server:9.0.5.0-x86_64-ubuntu-16.04`: You can also use the same environment variables you specified when [building](./building), for example, the following will try and test an image called `mqadvanced-server:9.1.2.0-amd64`:
``` ```
MQ_VERSION=9.0.5.0 make test-advancedserver MQ_VERSION=9.1.2.0 make test-advancedserver
``` ```
### Running the Docker tests with code coverage ### Running the Docker tests with code coverage

View File

@@ -5,7 +5,7 @@ In order to use the image, it is necessary to accept the terms of the IBM MQ lic
## Running with the default configuration ## Running with the default configuration
You can run a queue manager with the default configuration and a listener on port 1414 using the following command. For example, the following command creates and starts a queue manager called `QM1`, and maps port 1414 on the host to the MQ listener on port 1414 inside the container, as well as port 9443 on the host to the web console on port 9443 inside the container: You can run a queue manager with the default configuration and a listener on port 1414 using the following command. For example, the following command creates and starts a queue manager called `QM1`, and maps port 1414 on the host to the MQ listener on port 1414 inside the container, as well as port 9443 on the host to the web console on port 9443 inside the container:
``` ```sh
docker run \ docker run \
--env LICENSE=accept \ --env LICENSE=accept \
--env MQ_QMGR_NAME=QM1 \ --env MQ_QMGR_NAME=QM1 \
@@ -16,15 +16,15 @@ docker run \
``` ```
## Running with the default configuration and a volume ## Running with the default configuration and a volume
The above example will not persist any configuration data or messages across container runs. In order to do this, you need to use a [volume](https://docs.docker.com/engine/admin/volumes/volumes/). For example, you can create a volume with the following command: The above example will not persist any configuration data or messages across container runs. In order to do this, you need to use a [volume](https://docs.docker.com/storage/volumes/). For example, you can create a volume with the following command:
``` ```sh
docker volume create qm1data docker volume create qm1data
``` ```
You can then run a queue manager using this volume as follows: You can then run a queue manager using this volume as follows:
``` ```sh
docker run \ docker run \
--env LICENSE=accept \ --env LICENSE=accept \
--env MQ_QMGR_NAME=QM1 \ --env MQ_QMGR_NAME=QM1 \
@@ -40,7 +40,7 @@ The Docker image always uses `/mnt/mqm` for MQ data, which is correctly linked f
## Running with the default configuration and Prometheus metrics enabled ## Running with the default configuration and Prometheus metrics enabled
You can run a queue manager with [Prometheus](https://prometheus.io) metrics enabled. The following command will generate Prometheus metrics for your queue manager on `/metrics` port `9157`: You can run a queue manager with [Prometheus](https://prometheus.io) metrics enabled. The following command will generate Prometheus metrics for your queue manager on `/metrics` port `9157`:
``` ```sh
docker run \ docker run \
--env LICENSE=accept \ --env LICENSE=accept \
--env MQ_QMGR_NAME=QM1 \ --env MQ_QMGR_NAME=QM1 \
@@ -58,34 +58,36 @@ You can customize the configuration in several ways:
1. For getting started, you can use the [default developer configuration](developer-config.md), which is available out-of-the-box for the MQ Advanced for Developers image 1. For getting started, you can use the [default developer configuration](developer-config.md), which is available out-of-the-box for the MQ Advanced for Developers image
2. By creating your own image and adding your own MQSC file into the `/etc/mqm` directory on the image. This file will be run when your queue manager is created. 2. By creating your own image and adding your own MQSC file into the `/etc/mqm` directory on the image. This file will be run when your queue manager is created.
3. By using [remote MQ administration](http://www-01.ibm.com/support/knowledgecenter/SSFKSJ_9.0.0/com.ibm.mq.adm.doc/q021090_.htm), via an MQ command server, the MQ HTTP APIs, or using a tool such as the MQ web console or MQ Explorer. 3. By using [remote MQ administration](https://www.ibm.com/support/knowledgecenter/SSFKSJ_9.1.0/com.ibm.mq.adm.doc/q021090_.htm), via an MQ command server, the MQ HTTP APIs, or using a tool such as the MQ web console or MQ Explorer.
Note that a listener is always created on port 1414 inside the container. This port can be mapped to any port on the Docker host. Note that a listener is always created on port 1414 inside the container. This port can be mapped to any port on the Docker host.
The following is an *example* `Dockerfile` for creating your own pre-configured image, which adds a custom `config.mqsc` and an administrative user `alice`. Note that it is not normally recommended to include passwords in this way: The following is an *example* `Dockerfile` for creating your own pre-configured image, which adds a custom MQ configuration file, and an administrative user `alice`. Note that it is not normally recommended to include passwords in this way:
```dockerfile ```dockerfile
FROM ibmcom/mq FROM ibmcom/mq
USER root
RUN useradd alice -G mqm && \ RUN useradd alice -G mqm && \
echo alice:passw0rd | chpasswd echo alice:passw0rd | chpasswd
USER mqm
COPY 20-config.mqsc /etc/mqm/ COPY 20-config.mqsc /etc/mqm/
``` ```
Here is an example corresponding `20-config.mqsc` script from the [mqdev blog](https://www.ibm.com/developerworks/community/blogs/messaging/entry/getting_going_without_turning_off_mq_security?lang=en), which allows users with passwords to connect on the `PASSWORD.SVRCONN` channel: The `USER` instructions are necessary to ensure that the `useradd` and `chpasswd` commands are run as the root user.
Here is an example corresponding `20-config.mqsc` script, which creates two local queues:
```mqsc
DEFINE QLOCAL(MY.QUEUE.1) REPLACE
DEFINE QLOCAL(MY.QUEUE.2) REPLACE
``` ```
DEFINE CHANNEL(PASSWORD.SVRCONN) CHLTYPE(SVRCONN) REPLACE
SET CHLAUTH(PASSWORD.SVRCONN) TYPE(BLOCKUSER) USERLIST('nobody') DESCR('Allow privileged users on this channel') The file `20-config.mqsc` should be saved into the same directory as the `Dockerfile`.
SET CHLAUTH('*') TYPE(ADDRESSMAP) ADDRESS('*') USERSRC(NOACCESS) DESCR('BackStop rule')
SET CHLAUTH(PASSWORD.SVRCONN) TYPE(ADDRESSMAP) ADDRESS('*') USERSRC(CHANNEL) CHCKCLNT(REQUIRED)
ALTER AUTHINFO(SYSTEM.DEFAULT.AUTHINFO.IDPWOS) AUTHTYPE(IDPWOS) ADOPTCTX(YES)
REFRESH SECURITY TYPE(CONNAUTH)
```
## Running MQ commands ## Running MQ commands
It is recommended that you configure MQ in your own custom image. However, you may need to run MQ commands directly inside the process space of the container. To run a command against a running queue manager, you can use `docker exec`, for example: It is recommended that you configure MQ in your own custom image. However, you may need to run MQ commands directly inside the process space of the container. To run a command against a running queue manager, you can use `docker exec`, for example:
``` ```sh
docker exec \ docker exec \
--tty \ --tty \
--interactive \ --interactive \
@@ -94,3 +96,22 @@ docker exec \
``` ```
Using this technique, you can have full control over all aspects of the MQ installation. Note that if you use this technique to make changes to the filesystem, then those changes would be lost if you re-created your container unless you make those changes in volumes. Using this technique, you can have full control over all aspects of the MQ installation. Note that if you use this technique to make changes to the filesystem, then those changes would be lost if you re-created your container unless you make those changes in volumes.
## Supplying TLS certificates
If you wish to supply TLS Certificates that the queue manager and MQ Console should use for TLS operations then you must supply a PKCS#1 or unencrypted PKCS#8 PEM files for both the certificates and private keys in the following directories:
* `/etc/mqm/pki/keys/<Label>` - for certificates with public and private keys
* `/etc/mqm/pki/trust/<index>` - for certificates with only the public key
For example, if you have an identity certificate you wish to add with the label `mykey` and 2 certificates you wish to add as trusted then you would need to add the files into the following locations where files ending in `.key` contain private keys and `.crt` contain certificates:
- `/etc/mqm/pki/keys/mykey/tls.key`
- `/etc/mqm/pki/keys/mykey/tls.crt`
- `/etc/mqm/pki/keys/mykey/ca.crt`
- `/etc/mqm/pki/trust/0/tls.crt`
- `/etc/mqm/pki/trust/1/tls.crt`
This can be achieved by either mounting the directories or files into the container when you run it or by baking the files into the correct location in the image.
If you supply multiple identity certificates then the first label alphabetically will be chosen as the certificate to be used by the MQ Console and the default certificate for the queue manager. If you wish to use a different certificate on the queue manager then you can change the certificate to use at runtime by executing the MQSC command `ALTER QMGR CERTLABL('<newlabel>')`

19
etc/mqm/15-tls.mqsc.tpl Normal file
View File

@@ -0,0 +1,19 @@
* © Copyright IBM Corporation 2019
*
*
* 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.
* Set the keystore location for the queue manager
ALTER QMGR SSLKEYR('{{ .SSLKeyR }}')
ALTER QMGR CERTLABL('{{ .CertificateLabel }}')

8
glide.lock generated
View File

@@ -1,5 +1,5 @@
hash: b02555ebf3957ece0ae5ecf132fa4e415a4f66a7f4c27a82d484f4fb78f56e41 hash: 6ebd5fb1c39729378c7256da6f312e9699bff1ddff9941d3c8c1ba785e22acfd
updated: 2018-07-13T08:50:32.923040349+01:00 updated: 2019-05-21T10:38:01.227081+01:00
imports: imports:
- name: github.com/beorn7/perks - name: github.com/beorn7/perks
version: 3a771d992973f24aa725d07868b467d1ddfceafb version: 3a771d992973f24aa725d07868b467d1ddfceafb
@@ -50,4 +50,8 @@ imports:
version: 1b2967e3c290b7c545b3db0deeda16e9be4f98a2 version: 1b2967e3c290b7c545b3db0deeda16e9be4f98a2
subpackages: subpackages:
- unix - unix
- name: software.sslmate.com/src/go-pkcs12
version: 6e380ad96778cc63c6ea17649a9b74224bceafe9
subpackages:
- internal/rc2
testImports: [] testImports: []

View File

@@ -1,4 +1,4 @@
# © Copyright IBM Corporation 2017 # © Copyright IBM Corporation 2017, 2019
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -25,4 +25,6 @@ import:
- package: github.com/ibm-messaging/mq-golang - package: github.com/ibm-messaging/mq-golang
version: 2.0.0 version: 2.0.0
- package: github.com/genuinetools/amicontained - package: github.com/genuinetools/amicontained
version: 0.4.0 version: 0.4.0
- package: software.sslmate.com/src/go-pkcs12
commit: 6e380ad96778cc63c6ea17649a9b74224bceafe9

View File

@@ -1,4 +1,4 @@
# © Copyright IBM Corporation 2015, 2017 # © Copyright IBM Corporation 2015, 2019
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -15,14 +15,16 @@
FROM ubuntu:16.04 FROM ubuntu:16.04
# The URL to download the MQ installer from in tar.gz format # The URL to download the MQ installer from in tar.gz format
ARG MQ_URL=https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev903_ubuntu_x86-64.tar.gz ARG MQ_URL=https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev911_ubuntu_x86-64.tar.gz
# The MQ packages to install # The MQ packages to install
ARG MQ_PACKAGES="ibmmq-sfbridge" ARG MQ_PACKAGES="ibmmq-sfbridge"
ARG MQM_UID=999
ADD install-mq.sh /usr/local/bin/ ADD install-mq.sh /usr/local/bin/
RUN chmod u+x /usr/local/bin/install-mq.sh \ RUN chmod u+x /usr/local/bin/install-mq.sh \
&& install-mq.sh && install-mq.sh $MQM_UID
ENV LANG=en_US.UTF-8 ENV LANG=en_US.UTF-8

View File

@@ -1,7 +0,0 @@
This is a work-in-progress for a Docker image based on Red Hat Enterprise Linux (RHEL).
The current MQ container build requires Docker V17.05 or greater (required features include multi-stage Docker build, and "ARG"s in the "FROM" statement). Red Hat Enterprise Linux V7.5 includes Docker up to version V1.13.
In order to build images with Red Hat Enterprise Linux, license registration is required. The license of the host server can be used, as long as you either use Red Hat's patched version of Docker (which is an old version), or if you use alternative container management tools such as [`buildah`](https://github.com/projectatomic/buildah/) and `podman` (from [`libpod`](https://github.com/projectatomic/libpod)).
This directory contains scripts for building with `buildah`. The build itself isn't containerized, so more software than usual is needed on the RHEL host, so an Ansible playbook is also provided to help set up the host.

View File

@@ -1 +0,0 @@
hosts

View File

@@ -1,62 +0,0 @@
# © Copyright IBM Corporation 2018
#
# 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.
---
# Sets up a server for building the Red Hat image.
- hosts: rhbuild
become: true
any_errors_fatal: true
tasks:
- name: install buildah
package:
name: buildah
state: present
when: ansible_distribution == "RedHat"
- name: install podman
package:
name: buildah
state: present
when: ansible_distribution == "RedHat"
- name: install golang
package:
name: golang
state: absent
- name: install make
package:
name: make
state: present
- name: install git
package:
name: git
state: present
- name: install golang 1.10 from TAR
unarchive:
src: "https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz"
dest: "/usr/local"
remote_src: yes
# TODO: Re-factor to use get_url first, so we can use the checksum
#checksum: sha256:fa1b0e45d3b647c252f51f5e1204aba049cde4af177ef9f2181f43004f901035
creates: /usr/local/go/doc/go1.10.html
- name: add golang to PATH
copy:
dest: "/etc/profile.d/golang.sh"
content: |
PATH=$PATH:/usr/local/go/bin
- name: install dep from GitHub
get_url:
url: https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64
dest: /usr/local/bin/dep
mode: 0755
checksum: sha256:31144e465e52ffbc0035248a10ddea61a09bf28b00784fd3fdd9882c8cbb2315
when: ansible_architecture == "x86_64"
# TODO: Install MQ SDK

View File

@@ -1,141 +0,0 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2018
#
#
# 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.
# Build a RHEL image, using the buildah tool
set -x
set -e
MQ_ARCHIVE=downloads/mqadv_dev905_linux_x86-64.tar.gz
MQ_PACKAGES="MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesAMS-*.rpm"
# Use a "scratch" container, so the resulting image has minimal files
# Resulting image won't have yum, for example
ctr=$(buildah from scratch)
scratchmnt=$(buildah mount $ctr)
# Initialize yum for use with the scratch container
rpm --root $scratchmnt --initdb
yum install yum-utils
yumdownloader --destdir=/tmp redhat-release-server
rpm --root $scratchmnt -ihv /tmp/redhat-release-server*.rpm
# Install the packages required by MQ
yum install -y --installroot=$scratchmnt \
bash \
bc \
coreutils \
file \
findutils \
gawk \
glibc-common \
grep \
passwd \
procps-ng \
sed \
tar \
util-linux
# Clean up cached files
yum clean all --installroot=$scratchmnt
rm -rf $scratchmnt/var/cache/yum/*
groupadd --root $scratchmnt --system --gid 888 mqm
useradd --root $scratchmnt --system --uid 888 --gid mqm mqm
usermod --root $scratchmnt -G root mqm
DIR_EXTRACT=$scratchmnt/tmp/extract
mkdir -p $scratchmnt/tmp/extract
tar -zxvf ${MQ_ARCHIVE} -C ${DIR_EXTRACT}
DIR_RPM=$(find ${DIR_EXTRACT} -name "*.rpm" -printf "%h\n" | sort -u | head -1)
DIR_RPM=${DIR_RPM#$scratchmnt}
#DIR_RPM=$(buildah run $ctr -- find ${DIR_EXTRACT} -name "*.rpm" -printf "%h\n" | sort -u | head -1)
# Find location of mqlicense.sh
#MQLICENSE=$(buildah run $ctr -- find ${DIR_EXTRACT} -name "mqlicense.sh")
MQLICENSE=$(find ${DIR_EXTRACT} -name "mqlicense.sh")
MQLICENSE=${MQLICENSE#$scratchmnt}
# Accept the MQ license
buildah run $ctr -- ${MQLICENSE} -text_only -accept
buildah run $ctr -- bash -c "cd $DIR_RPM && rpm -ivh $MQ_PACKAGES"
rm -rf ${DIR_EXTRACT}
# Remove 32-bit libraries from 64-bit container
find $scratchmnt/opt/mqm $scratchmnt/var/mqm -type f -exec file {} \; | awk -F: '/ELF 32-bit/{print $1}' | xargs --no-run-if-empty rm -f
# Remove tar.gz files unpacked by RPM postinst scripts
find $scratchmnt/opt/mqm -name '*.tar.gz' -delete
# Recommended: Set the default MQ installation (makes the MQ commands available on the PATH)
buildah run $ctr -- /opt/mqm/bin/setmqinst -p /opt/mqm -i
# Remove the directory structure under /var/mqm which was created by the installer
rm -rf $scratchmnt/var/mqm
# Create the mount point for volumes
mkdir -p $scratchmnt/mnt/mqm
# Create the directory for MQ configuration files
mkdir -p $scratchmnt/etc/mqm
# Create a symlink for /var/mqm -> /mnt/mqm/data
buildah run $ctr ln -s /mnt/mqm/data /var/mqm
# Optional: Set these values for the Bluemix Vulnerability Report
sed -i 's/PASS_MAX_DAYS\t99999/PASS_MAX_DAYS\t90/' $scratchmnt/etc/login.defs
sed -i 's/PASS_MIN_DAYS\t0/PASS_MIN_DAYS\t1/' $scratchmnt/etc/login.defs
sed -i 's/password\t\[success=1 default=ignore\]\tpam_unix\.so obscure sha512/password\t[success=1 default=ignore]\tpam_unix.so obscure sha512 minlen=8/' $scratchmnt/etc/pam.d/password-auth
# Build and test the Go code
go build ./cmd/runmqserver/
go build ./cmd/chkmqready/
go build ./cmd/chkmqhealthy/
go test -v ./cmd/runmqserver/
go test -v ./cmd/chkmqready/
go test -v ./cmd/chkmqhealthy/
go test -v ./internal/...
go vet ./cmd/... ./internal/...
# Install the Go binaries into the image
cp runmqserver $scratchmnt/usr/local/bin/
cp chkmq* $scratchmnt/usr/local/bin/
cp NOTICES.txt $scratchmnt/opt/mqm/licenses/notices-container.txt
chmod ug+x $scratchmnt/usr/local/bin/runmqserver
chown mqm:mqm $scratchmnt/usr/local/bin/*mq*
chmod ug+xs $scratchmnt/usr/local/bin/chkmq*
buildah config \
--port 1414/tcp \
--port 9157/tcp \
--os linux \
--label architecture=x86_64 \
--label io.openshift.tags="mq messaging" \
--label io.k8s.display-name="IBM MQ Advanced Server" \
--label io.k8s.description="IBM MQ is messaging middleware that simplifies and accelerates the integration of diverse applications and business data across multiple platforms. It uses message queues to facilitate the exchanges of information and offers a single messaging solution for cloud, mobile, Internet of Things (IoT) and on-premises environments." \
--label name="mqadvanced-server" \
--label vendor="IBM" \
--label version="9.0.5.0" \
--env AMQ_ADDITIONAL_JSON_LOG=1 \
--env LANG=en_US.UTF-8 \
--env LOG_FORMAT=basic \
--entrypoint runmqserver \
--user 888 \
$ctr
buildah unmount $ctr
buildah commit $ctr mymq
# TODO: Leaves the working container lying around. Good for dev.

View File

@@ -1,4 +1,4 @@
# © Copyright IBM Corporation 2015, 2017 # © Copyright IBM Corporation 2015, 2019
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -12,27 +12,33 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
FROM ubuntu:16.04 FROM registry.access.redhat.com/ubi7/ubi-minimal AS mq-explorer
# The URL to download the MQ installer from in tar.gz format # The URL to download the MQ installer from in tar.gz format
ARG MQ_URL=https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev903_ubuntu_x86-64.tar.gz ARG MQ_URL="https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev912_linux_x86-64.tar.gz"
# The MQ packages to install # The MQ packages to install
ARG MQ_PACKAGES="ibmmq-explorer" ENV MQ_PACKAGES="MQSeriesRuntime*.rpm MQSeriesJRE*.rpm MQSeriesExplorer*.rpm"
RUN export DEBIAN_FRONTEND=noninteractive \ ARG MQM_UID=888
&& apt-get update \
&& apt-get install -y \ RUN microdnf install -y --nodocs gtk2 libXtst \
libgtk2.0-0 \ && microdnf clean all
libxtst6
ADD install-mq.sh /usr/local/bin/ ADD install-mq.sh /usr/local/bin/
# Install MQ Explorer. To avoid a "text file busy" error here, we sleep before installing.
# Need to re-instate the `/var/mqm` directory after installation, to avoid MQ
# errors with some commands (e.g. `dspmqver`)
RUN chmod u+x /usr/local/bin/install-mq.sh \ RUN chmod u+x /usr/local/bin/install-mq.sh \
&& install-mq.sh && sleep 1 \
&& install-mq.sh $MQM_UID \
&& rm -rf /var/mqm \
&& /opt/mqm/bin/crtmqdir -f -s
ENV LANG=en_US.UTF-8 ENV LANG=en_US.UTF-8
# Run as mqm (999) # Run as mqm
USER 999 USER $MQM_UID
ENTRYPOINT ["MQExplorer"] ENTRYPOINT ["MQExplorer"]

View File

@@ -1,5 +0,0 @@
# IBM MQ Software Developer Kit (SDK) with Go
This image contains the MQ SDK, Git, the Go compiler, and the `build-essential` package (which includes GNU C and C++ compilers plus other essential tools like `make`).
This image doesn't contain any Go code for MQ. You can add a CGO wrapper for the MQ C client, for example [mq-golang](https://github.com/ibm-messaging/mq-golang), via your vendor directory, or directly using `go get`.

View File

@@ -1,4 +1,4 @@
# © Copyright IBM Corporation 2018 # © Copyright IBM Corporation 2018, 2019
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -12,17 +12,19 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
ARG BASE_IMAGE=ubuntu:16.04 FROM registry.access.redhat.com/rhscl/devtoolset-7-toolchain-rhel7 AS mq-sdk
#FROM docker.io/centos/devtoolset-7-toolchain-centos7 AS mq-sdk
FROM $BASE_IMAGE
# The URL to download the MQ installer from in tar.gz format # The URL to download the MQ installer from in tar.gz format
# This assumes an archive containing the MQ Debian (.deb) install packages # This assumes an archive containing the MQ Debian (.deb) install packages
ARG MQ_URL ARG MQ_URL
# The packages to install in install-mq.sh # The packages to install in install-mq.sh
ARG MQ_PACKAGES ENV MQ_PACKAGES="MQSeriesRuntime-*.rpm MQSeriesSDK-*.rpm MQSeriesSamples*.rpm"
ENV MQM_UID=888
USER 0
COPY install-mq.sh /usr/local/bin/ COPY install-mq.sh /usr/local/bin/
# Install MQ. To avoid a "text file busy" error here, we sleep before installing. # Install MQ. To avoid a "text file busy" error here, we sleep before installing.
@@ -30,6 +32,7 @@ COPY install-mq.sh /usr/local/bin/
# errors with some commands (e.g. `dspmqver`) # errors with some commands (e.g. `dspmqver`)
RUN chmod u+x /usr/local/bin/install-mq.sh \ RUN chmod u+x /usr/local/bin/install-mq.sh \
&& sleep 1 \ && sleep 1 \
&& install-mq.sh \ && install-mq.sh $MQM_UID \
&& rm -rf /var/mqm \ && rm -rf /var/mqm \
&& /opt/mqm/bin/crtmqdir -f -s && /opt/mqm/bin/crtmqdir -f -s
USER 1001

View File

@@ -1,4 +1,4 @@
* © Copyright IBM Corporation 2017, 2018 * © Copyright IBM Corporation 2017, 2019
* *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
STOP LISTENER('SYSTEM.LISTENER.TCP.1') STOP LISTENER('SYSTEM.LISTENER.TCP.1') IGNSTATE(YES)
ALTER LISTENER('SYSTEM.LISTENER.TCP.1') TRPTYPE(TCP) CONTROL(MANUAL)
* Developer queues * Developer queues
DEFINE QLOCAL('DEV.QUEUE.1') REPLACE DEFINE QLOCAL('DEV.QUEUE.1') REPLACE
@@ -50,4 +51,4 @@ SET AUTHREC PROFILE('DEV.**') GROUP('mqclient') OBJTYPE(TOPIC) AUTHADD(PUB,SUB)
* Developer listener * Developer listener
DEFINE LISTENER('DEV.LISTENER.TCP') TRPTYPE(TCP) PORT(1414) CONTROL(QMGR) REPLACE DEFINE LISTENER('DEV.LISTENER.TCP') TRPTYPE(TCP) PORT(1414) CONTROL(QMGR) REPLACE
START LISTENER('DEV.LISTENER.TCP') START LISTENER('DEV.LISTENER.TCP') IGNSTATE(YES)

View File

@@ -1,4 +1,4 @@
* © Copyright IBM Corporation 2018 * © Copyright IBM Corporation 2018, 2019
* *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,10 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
* Set the keystore location for the queue manager
ALTER QMGR SSLKEYR('{{ .SSLKeyR }}')
ALTER QMGR CERTLABL('{{ .CertificateLabel }}')
* Set the cipherspec for dev channels * Set the cipherspec for dev channels
ALTER CHANNEL('DEV.APP.SVRCONN') CHLTYPE(SVRCONN) SSLCIPH({{ .SSLCipherSpec }}) SSLCAUTH(OPTIONAL) ALTER CHANNEL('DEV.APP.SVRCONN') CHLTYPE(SVRCONN) SSLCIPH({{ .SSLCipherSpec }}) SSLCAUTH(OPTIONAL)
ALTER CHANNEL('DEV.ADMIN.SVRCONN') CHLTYPE(SVRCONN) SSLCIPH({{ .SSLCipherSpec }}) SSLCAUTH(OPTIONAL) ALTER CHANNEL('DEV.ADMIN.SVRCONN') CHLTYPE(SVRCONN) SSLCIPH({{ .SSLCipherSpec }}) SSLCAUTH(OPTIONAL)

View File

@@ -1,66 +0,0 @@
# © Copyright IBM Corporation 2015, 2018
#
# 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.
ARG BASE_IMAGE=mqadvanced-server-dev-base:9.0.5.0-x86_64-ubuntu-16.04
ARG BUILDER_IMAGE=mq-golang-sdk:9.0.5.0-x86_64-ubuntu-16.04
###############################################################################
# Build stage to build Go code
###############################################################################
FROM $BUILDER_IMAGE as builder
ARG IMAGE_REVISION="Not specified"
ARG IMAGE_CREATED="Not specified"
ARG IMAGE_SOURCE="Not specified"
WORKDIR /go/src/github.com/ibm-messaging/mq-container/
COPY cmd/ ./cmd
COPY internal/ ./internal
COPY vendor/ ./vendor
# Re-build runmqserver, with code tagged with 'mqdev' enabled
RUN go build -ldflags "-X \"main.ImageCreated=$IMAGE_CREATED\" -X \"main.ImageRevision=$IMAGE_REVISION\" -X \"main.ImageSource=$IMAGE_SOURCE\"" --tags 'mqdev' ./cmd/runmqserver
RUN go build ./cmd/runmqdevserver/
# Run all unit tests
RUN go test -v ./cmd/runmqdevserver/...
###############################################################################
# Main build stage
###############################################################################
FROM $BASE_IMAGE
# Enable MQ developer default configuration
ENV MQ_DEV=true
# Default administrator password
ENV MQ_ADMIN_PASSWORD=passw0rd
## Add admin and app users, and set a default password for admin
RUN useradd admin -G mqm \
&& groupadd mqclient \
&& useradd app -G mqclient \
&& echo admin:$MQ_ADMIN_PASSWORD | chpasswd
# Create a directory for runtime data from runmqserver
RUN mkdir -p /run/runmqdevserver \
&& chown mqm:mqm /run/runmqdevserver
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqserver /usr/local/bin/
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqdevserver /usr/local/bin/
# Copy template files
COPY incubating/mqadvanced-server-dev/*.tpl /etc/mqm/
# Copy web XML files for default developer configuration
COPY incubating/mqadvanced-server-dev/web /etc/mqm/web
RUN chmod +x /usr/local/bin/runmq*
EXPOSE 9443
ENTRYPOINT ["runmqdevserver"]

View File

@@ -1,17 +0,0 @@
#!/bin/bash
# Change admin password
if [ -n "${MQ_ADMIN_PASSWORD}" ]; then
echo admin:${MQ_ADMIN_PASSWORD} | chpasswd
fi
# Change app password
if [ -n "${MQ_APP_PASSWORD}" ]; then
echo app:${MQ_APP_PASSWORD} | chpasswd
fi
# Delete the MQSC with developer defaults, if requested
if [ "${MQ_DEV}" != "true" ]; then
rm -f /etc/mqm/dev.mqsc
fi
exec runmqserver

View File

@@ -0,0 +1,38 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2019
#
#
# 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.
test -f /usr/bin/yum && YUM=true || YUM=false
test -f /usr/bin/microdnf && MICRODNF=true || MICRODNF=false
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
rm -rf /var/lib/apt/lists/*
fi
if ($YUM); then
yum -y install sudo
yum -y clean all
rm -rf /var/cache/yum/*
fi
if ($MICRODNF); then
microdnf install --nodocs sudo
microdnf clean all
fi

View File

@@ -16,14 +16,25 @@
<security-role name="MQWebAdmin"> <security-role name="MQWebAdmin">
<group name="MQWebUI" realm="defaultRealm"/> <group name="MQWebUI" realm="defaultRealm"/>
</security-role> </security-role>
<security-role name="MQWebUser">
<group name="MQWebMessaging" realm="defaultRealm"/>
</security-role>
</application-bnd> </application-bnd>
</enterpriseApplication> </enterpriseApplication>
<basicRegistry id="basic" realm="defaultRealm"> <basicRegistry id="basic" realm="defaultRealm">
<user name="admin" password="${env.MQ_ADMIN_PASSWORD}"/> <user name="admin" password="${env.MQ_ADMIN_PASSWORD}"/>
<!-- The app user will always get a default password of "passw0rd",
even if you don't set the environment variable.
See `webserver.go` -->
<user name="app" password="${env.MQ_APP_PASSWORD}"/>
<group name="MQWebUI"> <group name="MQWebUI">
<member name="admin"/> <member name="admin"/>
</group> </group>
<group name="MQWebMessaging">
<member name="app"/>
</group>
</basicRegistry> </basicRegistry>
<variable name="httpHost" value="*"/> <variable name="httpHost" value="*"/>
<variable name="managementMode" value="externallyprovisioned"/>
<include location="tls.xml"/> <include location="tls.xml"/>
</server> </server>

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<server>
<keyStore id="MQWebKeyStore" location="/run/runmqdevserver/tls/key.jks" type="JKS" password="${env.MQ_TLS_PASSPHRASE}"/>
<keyStore id="MQWebTrustStore" location="/run/runmqdevserver/tls/trust.jks" type="JKS" password="${env.MQ_TLS_PASSPHRASE}"/>
<ssl id="thisSSLConfig" clientAuthenticationSupported="true" keyStoreRef="MQWebKeyStore" trustStoreRef="MQWebTrustStore" sslProtocol="TLSv1.2" serverKeyAlias="devcert"/>
<sslDefault sslRef="thisSSLConfig"/>
</server>

26
install-build-deps-ubuntu.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2015, 2019
#
#
# 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.
# Install Docker and dep, required by build (assumes Ubuntu host, as used by Travis build)
set -ex
curl https://glide.sh/get | sh
sudo curl -Lo /usr/local/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64
sudo chmod +x /usr/local/bin/dep
go get -u golang.org/x/lint/golint

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# -*- mode: sh -*- # -*- mode: sh -*-
# © Copyright IBM Corporation 2018 # © Copyright IBM Corporation 2015, 2019
# #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,9 @@
# Fail on any non-zero return code # Fail on any non-zero return code
set -ex set -ex
test -f /usr/bin/yum && RHEL=true || RHEL=false test -f /usr/bin/yum && YUM=true || YUM=false
test -f /usr/bin/microdnf && MICRODNF=true || MICRODNF=false
test -f /usr/bin/rpm && RPM=true || RPM=false
test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false
if ($UBUNTU); then if ($UBUNTU); then
@@ -37,37 +39,43 @@ if ($UBUNTU); then
# This ensures no unsupported code gets installed, and makes the build faster # This ensures no unsupported code gets installed, and makes the build faster
echo "deb ${APT_URL} ${UBUNTU_CODENAME} main restricted" > /etc/apt/sources.list echo "deb ${APT_URL} ${UBUNTU_CODENAME} main restricted" > /etc/apt/sources.list
echo "deb ${APT_URL} ${UBUNTU_CODENAME}-updates main restricted" >> /etc/apt/sources.list echo "deb ${APT_URL} ${UBUNTU_CODENAME}-updates main restricted" >> /etc/apt/sources.list
echo "deb ${APT_URL} ${UBUNTU_CODENAME}-backports main restricted universe" >> /etc/apt/sources.list;
echo "deb ${APT_URL} ${UBUNTU_CODENAME}-security main restricted" >> /etc/apt/sources.list echo "deb ${APT_URL} ${UBUNTU_CODENAME}-security main restricted" >> /etc/apt/sources.list
apt-get update
apt-get install -y --no-install-recommends \
golang-${GO_VERSION} \
git \
ca-certificates
fi
if ($RHEL); then
# Install additional packages required by MQ, this install process and the runtime scripts # Install additional packages required by MQ, this install process and the runtime scripts
yum -y install \ apt-get update
git \ apt-get install -y --no-install-recommends \
bash \
bc \
ca-certificates \
coreutils \
curl \ curl \
debianutils \
file \
findutils \
gawk \
grep \
libc-bin \
mount \
passwd \
procps \
sed \
tar \ tar \
gcc util-linux
cd /tmp
curl -LO https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz
tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
fi fi
# Remove any orphaned packages if ($RPM); then
$UBUNTU && apt-get autoremove -y EXTRA_RPMS="bash bc ca-certificates coreutils file findutils gawk glibc-common grep 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 --nodocs ${EXTRA_RPMS}
fi
# Apply any bug fixes not included in base Ubuntu or MQ image.
# Don't upgrade everything based on Docker best practices https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#run
$UBUNTU && apt-get install -y libapparmor1 libsystemd0 systemd systemd-sysv libudev1 perl-base --only-upgrade
# End of bug fixes
# Clean up cached files # Clean up cached files
$UBUNTU && rm -rf /var/lib/apt/lists/* $UBUNTU && rm -rf /var/lib/apt/lists/*
$RHEL && yum -y clean all $YUM && yum -y clean all
$RHEL && rm -rf /var/cache/yum/* $YUM && rm -rf /var/cache/yum/*
$MICRODNF && microdnf clean all
# Make the GOLANG directories
mkdir -p $GOPATH/src $GOPATH/bin
chmod -R 777 $GOPATH

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# -*- mode: sh -*- # -*- mode: sh -*-
# © Copyright IBM Corporation 2015, 2018 # © Copyright IBM Corporation 2015, 2019
# #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,99 +18,27 @@
# Fail on any non-zero return code # Fail on any non-zero return code
set -ex set -ex
test -f /usr/bin/yum && RHEL=true || RHEL=false mqm_uid=${1:-888}
test -f /usr/bin/yum && YUM=true || YUM=false
test -f /usr/bin/microdnf && MICRODNF=true || MICRODNF=false
test -f /usr/bin/rpm && RPM=true || RPM=false
test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false
# If MQ_PACKAGES isn't specifically set, then choose a valid set of defaults
if [ -z "$MQ_PACKAGES" ]; then
$UBUNTU && MQ_PACKAGES="ibmmq-server ibmmq-java ibmmq-jre ibmmq-gskit ibmmq-msg-.* ibmmq-samples ibmmq-ams"
$RHEL && MQ_PACKAGES="MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesAMS-*.rpm"
fi
if ($UBUNTU); then
export DEBIAN_FRONTEND=noninteractive
# Use a reduced set of apt repositories.
# This ensures no unsupported code gets installed, and makes the build faster
source /etc/os-release
# Figure out the correct apt URL based on the CPU architecture
CPU_ARCH=$(uname -p)
if [ ${CPU_ARCH} == "x86_64" ]; then
APT_URL="http://archive.ubuntu.com/ubuntu/"
else
APT_URL="http://ports.ubuntu.com/ubuntu-ports/"
fi
# Use a reduced set of apt repositories.
# This ensures no unsupported code gets installed, and makes the build faster
echo "deb ${APT_URL} ${UBUNTU_CODENAME} main restricted" > /etc/apt/sources.list
echo "deb ${APT_URL} ${UBUNTU_CODENAME}-updates main restricted" >> /etc/apt/sources.list
echo "deb ${APT_URL} ${UBUNTU_CODENAME}-security main restricted" >> /etc/apt/sources.list
# Install additional packages required by MQ, this install process and the runtime scripts
apt-get update
apt-get install -y --no-install-recommends \
bash \
bc \
ca-certificates \
coreutils \
curl \
debianutils \
file \
findutils \
gawk \
grep \
libc-bin \
mount \
passwd \
procps \
sed \
tar \
util-linux
fi
# Install additional packages required by MQ, this install process and the runtime scripts
$RHEL && yum -y install \
bash \
bc \
ca-certificates \
coreutils \
curl \
file \
findutils \
gawk \
glibc-common \
grep \
passwd \
procps-ng \
sed \
tar \
util-linux
# Download and extract the MQ installation files # Download and extract the MQ installation files
DIR_EXTRACT=/tmp/mq DIR_EXTRACT=/tmp/mq
mkdir -p ${DIR_EXTRACT} mkdir -p ${DIR_EXTRACT}
cd ${DIR_EXTRACT} cd ${DIR_EXTRACT}
curl -LO $MQ_URL curl -LO $MQ_URL
tar -zxvf ./*.tar.gz tar -zxf ./*.tar.gz
# Remove packages only needed by this script
$UBUNTU && apt-get purge -y \
ca-certificates \
curl
# Note: ca-certificates and curl are installed by default in RHEL
# Remove any orphaned packages
$UBUNTU && apt-get autoremove -y
# Recommended: Create the mqm user ID with a fixed UID and group, so that the file permissions work between different images # Recommended: Create the mqm user ID with a fixed UID and group, so that the file permissions work between different images
$UBUNTU && groupadd --system --gid 999 mqm groupadd --system --gid ${mqm_uid} mqm
$UBUNTU && useradd --system --uid 999 --gid mqm mqm useradd --system --uid ${mqm_uid} --gid mqm --groups 0 mqm
$RHEL && groupadd --system --gid 888 mqm
$RHEL && useradd --system --uid 888 --gid mqm mqm
usermod -G mqm root
# Find directory containing .deb files # Find directory containing .deb files
$UBUNTU && DIR_DEB=$(find ${DIR_EXTRACT} -name "*.deb" -printf "%h\n" | sort -u | head -1) $UBUNTU && DIR_DEB=$(find ${DIR_EXTRACT} -name "*.deb" -printf "%h\n" | sort -u | head -1)
$RHEL && DIR_RPM=$(find ${DIR_EXTRACT} -name "*.rpm" -printf "%h\n" | sort -u | head -1) $RPM && DIR_RPM=$(find ${DIR_EXTRACT} -name "*.rpm" -printf "%h\n" | sort -u | head -1)
# Find location of mqlicense.sh # Find location of mqlicense.sh
MQLICENSE=$(find ${DIR_EXTRACT} -name "mqlicense.sh") MQLICENSE=$(find ${DIR_EXTRACT} -name "mqlicense.sh")
@@ -122,10 +50,11 @@ $UBUNTU && echo "deb [trusted=yes] file:${DIR_DEB} ./" > /etc/apt/sources.list.d
$UBUNTU && apt-get update $UBUNTU && apt-get update
$UBUNTU && apt-get install -y $MQ_PACKAGES $UBUNTU && apt-get install -y $MQ_PACKAGES
$RHEL && cd $DIR_RPM && rpm -ivh $MQ_PACKAGES $RPM && cd $DIR_RPM && rpm -ivh $MQ_PACKAGES
# Remove 32-bit libraries from 64-bit container # Remove 32-bit libraries from 64-bit container
find /opt/mqm /var/mqm -type f -exec file {} \; | awk -F: '/ELF 32-bit/{print $1}' | xargs --no-run-if-empty rm -f # The "file" utility isn't installed by default in UBI, so only try this if it's installed
which file && find /opt/mqm /var/mqm -type f -exec file {} \; | awk -F: '/ELF 32-bit/{print $1}' | xargs --no-run-if-empty rm -f
# Remove tar.gz files unpacked by RPM postinst scripts # Remove tar.gz files unpacked by RPM postinst scripts
find /opt/mqm -name '*.tar.gz' -delete find /opt/mqm -name '*.tar.gz' -delete
@@ -137,35 +66,41 @@ find /opt/mqm -name '*.tar.gz' -delete
$UBUNTU && rm -f /etc/apt/sources.list.d/IBM_MQ.list $UBUNTU && rm -f /etc/apt/sources.list.d/IBM_MQ.list
rm -rf ${DIR_EXTRACT} rm -rf ${DIR_EXTRACT}
# Apply any bug fixes not included in base Ubuntu or MQ image.
# Don't upgrade everything based on Docker best practices https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#run
$UBUNTU && apt-get install -y gcc-5-base gnupg gpgv libgcrypt20 libstdc++6 perl-base --only-upgrade
# End of bug fixes
# Clean up cached files
$UBUNTU && rm -rf /var/lib/apt/lists/*
$RHEL && yum -y clean all
$RHEL && rm -rf /var/cache/yum/*
# Optional: Update the command prompt with the MQ version # Optional: Update the command prompt with the MQ version
$UBUNTU && echo "mq:$(dspmqver -b -f 2)" > /etc/debian_chroot $UBUNTU && echo "mq:$(dspmqver -b -f 2)" > /etc/debian_chroot
# Remove the directory structure under /var/mqm which was created by the installer # Remove the directory structure under /var/mqm which was created by the installer
rm -rf /var/mqm rm -rf /var/mqm
# Create the mount point for volumes # Create the mount point for volumes, ensuring MQ has permissions to all directories
mkdir -p /mnt/mqm install --directory --mode 0775 --owner mqm --group root /mnt
install --directory --mode 0775 --owner mqm --group root /mnt/mqm
install --directory --mode 0775 --owner mqm --group root /mnt/mqm/data
install --directory --mode 0775 --owner mqm --group root /mnt/mqm-log
install --directory --mode 0775 --owner mqm --group root /mnt/mqm-log/log
install --directory --mode 0775 --owner mqm --group root /mnt/mqm-data
install --directory --mode 0775 --owner mqm --group root /mnt/mqm-data/qmgrs
# Create the directory for MQ configuration files # Create the directory for MQ configuration files
mkdir -p /etc/mqm install --directory --mode 0775 --owner mqm --group root /etc/mqm
# Create a symlink for /var/mqm -> /mnt/mqm/data # Create a symlink for /var/mqm -> /mnt/mqm/data
ln -s /mnt/mqm/data /var/mqm ln -s /mnt/mqm/data /var/mqm
# Optional: Set these values for the Bluemix Vulnerability Report # Optional: Ensure any passwords expire in a timely manner
sed -i 's/PASS_MAX_DAYS\t99999/PASS_MAX_DAYS\t90/' /etc/login.defs sed -i 's/PASS_MAX_DAYS\t99999/PASS_MAX_DAYS\t90/' /etc/login.defs
sed -i 's/PASS_MIN_DAYS\t0/PASS_MIN_DAYS\t1/' /etc/login.defs sed -i 's/PASS_MIN_DAYS\t0/PASS_MIN_DAYS\t1/' /etc/login.defs
sed -i 's/PASS_MIN_LEN\t5/PASS_MIN_LEN\t8/' /etc/login.defs
sed -i 's/# minlen = 9/minlen = 8/' /etc/security/pwquality.conf
$UBUNTU && PAM_FILE=/etc/pam.d/common-password $UBUNTU && PAM_FILE=/etc/pam.d/common-password
$RHEL && PAM_FILE=/etc/pam.d/password-auth $RPM && PAM_FILE=/etc/pam.d/password-auth
sed -i 's/password\t\[success=1 default=ignore\]\tpam_unix\.so obscure sha512/password\t[success=1 default=ignore]\tpam_unix.so obscure sha512 minlen=8/' $PAM_FILE sed -i 's/password\t\[success=1 default=ignore\]\tpam_unix\.so obscure sha512/password\t[success=1 default=ignore]\tpam_unix.so obscure sha512 minlen=8/' $PAM_FILE
# List all the installed packages, for the build log
$RPM && rpm -q --all || true
$UBUNTU && dpkg --list || true
# Copy MQ Licenses into the correct location
mkdir -p /licenses
cp /opt/mqm/licenses/*.txt /licenses/

View File

@@ -53,11 +53,13 @@ func RunCmd(cmd *exec.Cmd) (string, int, error) {
// Do not use this function to run shell built-ins (like "cd"), because // Do not use this function to run shell built-ins (like "cd"), because
// the error handling works differently // the error handling works differently
func Run(name string, arg ...string) (string, int, error) { func Run(name string, arg ...string) (string, int, error) {
// #nosec G204
return RunCmd(exec.Command(name, arg...)) return RunCmd(exec.Command(name, arg...))
} }
// RunAsMQM runs the specified command as the mqm user // RunAsMQM runs the specified command as the mqm user
func RunAsMQM(name string, arg ...string) (string, int, error) { func RunAsMQM(name string, arg ...string) (string, int, error) {
// #nosec G204
cmd := exec.Command(name, arg...) cmd := exec.Command(name, arg...)
cmd.SysProcAttr = &syscall.SysProcAttr{} cmd.SysProcAttr = &syscall.SysProcAttr{}
uid, gid, err := LookupMQM() uid, gid, err := LookupMQM()

View File

@@ -0,0 +1,128 @@
/*
© Copyright IBM Corporation 2019
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 containerruntime
import (
"fmt"
"io/ioutil"
"strings"
"github.com/genuinetools/amicontained/container"
)
func GetContainerRuntime() (string, error) {
return container.DetectRuntime()
}
func GetBaseImage() (string, error) {
buf, err := ioutil.ReadFile("/etc/os-release")
if err != nil {
return "", fmt.Errorf("Failed to read /etc/os-release: %v", err)
}
lines := strings.Split(string(buf), "\n")
for _, l := range lines {
if strings.HasPrefix(l, "PRETTY_NAME=") {
words := strings.Split(l, "\"")
if len(words) >= 2 {
return words[1], nil
}
}
}
return "unknown", nil
}
// GetCapabilities gets the Linux capabilities (e.g. setuid, setgid). See https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
func GetCapabilities() (map[string][]string, error) {
return container.Capabilities()
}
// GetSeccomp gets the seccomp enforcing mode, which affects which kernel calls can be made
func GetSeccomp() (string, error) {
s, err := container.SeccompEnforcingMode()
if err != nil {
return "", fmt.Errorf("Failed to get container SeccompEnforcingMode: %v", err)
}
return s, nil
}
// GetSecurityAttributes gets the security attributes of the current process.
// The security attributes indicate whether AppArmor or SELinux are being used,
// and what the level of confinement is.
func GetSecurityAttributes() string {
a, err := readProc("/proc/self/attr/current")
// On some systems, if AppArmor or SELinux are not installed, you get an
// error when you try and read `/proc/self/attr/current`, even though the
// file exists.
if err != nil || a == "" {
a = "none"
}
return a
}
func readProc(filename string) (value string, err error) {
// #nosec G304
buf, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
return strings.TrimSpace(string(buf)), nil
}
func GetMounts() (map[string]string, error) {
all, err := readProc("/proc/mounts")
if err != nil {
return nil, fmt.Errorf("Couldn't read /proc/mounts")
}
result := make(map[string]string)
lines := strings.Split(all, "\n")
for i := range lines {
parts := strings.Split(lines[i], " ")
//dev := parts[0]
mountPoint := parts[1]
fsType := parts[2]
if strings.Contains(mountPoint, "/mnt/mqm") {
result[mountPoint] = fsType
}
}
return result, nil
}
func GetKernelVersion() (string, error) {
return readProc("/proc/sys/kernel/osrelease")
}
func GetMaxFileHandles() (string, error) {
return readProc("/proc/sys/fs/file-max")
}
// SupportedFilesystem returns true if the supplied filesystem type is supported for MQ data
func SupportedFilesystem(fsType string) bool {
switch fsType {
case "aufs", "overlayfs", "tmpfs":
return false
default:
return true
}
}
// ValidMultiInstanceFilesystem returns true if the supplied filesystem type is valid for a multi-instance queue manager
func ValidMultiInstanceFilesystem(fsType string) bool {
if !SupportedFilesystem(fsType) {
return false
}
// TODO : check for non-shared filesystems & shared filesystems which are known not to work
return true
}

View File

@@ -1,7 +1,7 @@
// +build linux // +build linux
/* /*
© Copyright IBM Corporation 2017, 2018 © Copyright IBM Corporation 2017, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,11 +15,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package main package containerruntime
import ( import (
"fmt"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@@ -101,24 +99,17 @@ var fsTypes = map[int64]string{
0x58295829: "zsmalloc", 0x58295829: "zsmalloc",
} }
func checkFS(path string) error { // GetFilesystem returns the filesystem type for the specified path
func GetFilesystem(path string) (string, error) {
statfs := &unix.Statfs_t{} statfs := &unix.Statfs_t{}
err := unix.Statfs(path, statfs) err := unix.Statfs(path, statfs)
if err != nil { if err != nil {
log.Println(err) return "", err
return nil
} }
// Use a type conversion to make type an int64. On s390x it's a uint32. // Use a type conversion to make type an int64. On s390x it's a uint32.
t, ok := fsTypes[int64(statfs.Type)] t, ok := fsTypes[int64(statfs.Type)]
if !ok { if !ok {
log.Printf("WARNING: detected %v has unknown filesystem type %x", path, statfs.Type) return "unknown", nil
return nil
}
switch t {
case "aufs", "overlayfs", "tmpfs":
return fmt.Errorf("%v uses unsupported filesystem type: %v", path, t)
default:
log.Printf("Detected %v has filesystem type '%v'", path, t)
return nil
} }
return t, nil
} }

View File

@@ -1,7 +1,7 @@
// +build !linux // +build !linux
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package main package containerruntime
// Dummy version of this function, only for non-Linux systems. // Dummy version of this function, only for non-Linux systems.
// Having this allows unit tests to be run on other platforms (e.g. macOS) // Having this allows unit tests to be run on other platforms (e.g. macOS)

View File

@@ -0,0 +1,103 @@
/*
© Copyright IBM Corporation 2017, 2019
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 containerruntimelogger
import (
"fmt"
"os"
"runtime"
"strings"
"github.com/ibm-messaging/mq-container/internal/containerruntime"
"github.com/ibm-messaging/mq-container/internal/logger"
"github.com/ibm-messaging/mq-container/internal/user"
)
// LogContainerDetails logs details about the container runtime
func LogContainerDetails(log *logger.Logger) error {
if runtime.GOOS != "linux" {
return fmt.Errorf("Unsupported platform: %v", runtime.GOOS)
}
log.Printf("CPU architecture: %v", runtime.GOARCH)
kv, err := containerruntime.GetKernelVersion()
if err == nil {
log.Printf("Linux kernel version: %v", kv)
}
cr, err := containerruntime.GetContainerRuntime()
if err == nil {
log.Printf("Container runtime: %v", cr)
}
bi, err := containerruntime.GetBaseImage()
if err == nil {
log.Printf("Base image: %v", bi)
}
u, err := user.GetUser()
if err == nil {
if len(u.SupplementalGID) == 0 {
log.Printf("Running as user ID %v (%v) with primary group %v", u.UID, u.Name, u.PrimaryGID)
} else {
log.Printf("Running as user ID %v (%v) with primary group %v, and supplementary groups %v", u.UID, u.Name, u.PrimaryGID, strings.Join(u.SupplementalGID, ","))
}
}
caps, err := containerruntime.GetCapabilities()
capLogged := false
if err == nil {
for k, v := range caps {
if len(v) > 0 {
log.Printf("Capabilities (%s set): %v", strings.ToLower(k), strings.Join(v, ","))
capLogged = true
}
}
if !capLogged {
log.Print("Capabilities: none")
}
} else {
log.Errorf("Error getting capabilities: %v", err)
}
sc, err := containerruntime.GetSeccomp()
if err == nil {
log.Printf("seccomp enforcing mode: %v", sc)
}
log.Printf("Process security attributes: %v", containerruntime.GetSecurityAttributes())
m, err := containerruntime.GetMounts()
if err == nil {
if len(m) == 0 {
log.Print("No volume detected. Persistent messages may be lost")
} else {
for mountPoint, fsType := range m {
log.Printf("Detected '%v' volume mounted to %v", fsType, mountPoint)
if !containerruntime.SupportedFilesystem(fsType) {
return fmt.Errorf("%v uses unsupported filesystem type: %v", mountPoint, fsType)
}
}
}
}
// For a multi-instance queue manager - check all required mounts exist & validate filesystem type
if os.Getenv("MQ_MULTI_INSTANCE") == "true" {
log.Println("Multi-instance queue manager: enabled")
reqMounts := []string{"/mnt/mqm", "/mnt/mqm-log", "/mnt/mqm-data"}
for _, mountPoint := range reqMounts {
if fsType, ok := m[mountPoint]; ok {
if !containerruntime.ValidMultiInstanceFilesystem(fsType) {
return fmt.Errorf("%v uses filesystem type '%v' which is invalid for a multi-instance queue manager", mountPoint, fsType)
}
} else {
return fmt.Errorf("Missing required mount '%v' for a multi-instance queue manager", mountPoint)
}
}
}
return nil
}

58
internal/copy/copy.go Normal file
View File

@@ -0,0 +1,58 @@
/*
© Copyright IBM Corporation 2019
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 copy
import (
"fmt"
"io"
"os"
"github.com/ibm-messaging/mq-container/internal/filecheck"
)
func CopyFileMode(src, dest string, perm os.FileMode) error {
err := filecheck.CheckFileSource(src)
if err != nil {
return fmt.Errorf("failed to open %s for copy: %v", src, err)
}
// #nosec G304 - filename variable 'src' is checked above to ensure it is valid
in, err := os.Open(src)
if err != nil {
return fmt.Errorf("failed to open %s for copy: %v", src, err)
}
defer in.Close()
out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, perm)
if err != nil {
return fmt.Errorf("failed to open %s for copy: %v", dest, err)
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
err = out.Close()
return err
}
// CopyFile copies the specified file
func CopyFile(src, dest string) error {
return CopyFileMode(src, dest, 0770)
}

View File

@@ -1,7 +1,5 @@
// +build !mqdev
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,8 +13,25 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package main
func postInit(name string) error { package filecheck
import (
"fmt"
"path/filepath"
"strings"
)
// CheckFileSource checks the filename is valid
func CheckFileSource(fileName string) error {
absFile, _ := filepath.Abs(fileName)
prefixes := []string{"bin", "boot", "dev", "lib", "lib64", "proc", "sbin", "sys"}
for _, prefix := range prefixes {
if strings.HasPrefix(absFile, filepath.Join("/", prefix)) {
return fmt.Errorf("Filename resolves to invalid path '%v'", absFile)
}
}
return nil return nil
} }

View File

@@ -0,0 +1,40 @@
/*
© Copyright IBM Corporation 2019
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 filecheck
import (
"testing"
)
func TestCheckFileSource(t *testing.T) {
invalidFilenames := []string{"/bin", "/boot", "/dev", "/lib", "/lib64", "/proc", "/sbin", "/sys", "/bin/myfile", "/boot/mydir/myfile", "/var/../dev", "/var/../lib/myfile"}
for _, invalidFilename := range invalidFilenames {
err := CheckFileSource(invalidFilename)
if err == nil {
t.Errorf("Expected to receive an error for filename '%v'", invalidFilename)
}
}
validFilenames := []string{"/var", "/mydir/dev", "/mydir/dev/myfile"}
for _, validFilename := range validFilenames {
err := CheckFileSource(validFilename)
if err != nil {
t.Errorf("Unexpected error received: %v", err)
}
}
}

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,12 +13,15 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package main
// Package keystore contains code to create and update keystores
package keystore
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -53,23 +56,44 @@ func NewCMSKeyStore(filename, password string) *KeyStore {
} }
} }
// NewPKCS12KeyStore creates a new PKCS12 Key Store, managed by the runmqakm command
func NewPKCS12KeyStore(filename, password string) *KeyStore {
return &KeyStore{
Filename: filename,
Password: password,
keyStoreType: "p12",
command: "/opt/mqm/bin/runmqakm",
}
}
// Create a key store, if it doesn't already exist // Create a key store, if it doesn't already exist
func (ks *KeyStore) Create() error { func (ks *KeyStore) Create() error {
_, err := os.Stat(ks.Filename) _, err := os.Stat(ks.Filename)
if err == nil { if err == nil {
// Keystore already exists so we should refresh it by deleting it. // Keystore already exists so we should refresh it by deleting it.
extension := filepath.Ext(ks.Filename) extension := filepath.Ext(ks.Filename)
log.Debugf("Refreshing keystore: %v", ks.Filename)
if ks.keyStoreType == "cms" { if ks.keyStoreType == "cms" {
// Only delete these when we are refreshing the kdb keystore // Only delete these when we are refreshing the kdb keystore
stashFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".sth" stashFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".sth"
rdbFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".rdb" rdbFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".rdb"
crlFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".crl" crlFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".crl"
os.Remove(stashFile) err = os.Remove(stashFile)
os.Remove(rdbFile) if err != nil {
os.Remove(crlFile) return err
}
err = os.Remove(rdbFile)
if err != nil {
return err
}
err = os.Remove(crlFile)
if err != nil {
return err
}
}
err = os.Remove(ks.Filename)
if err != nil {
return err
} }
os.Remove(ks.Filename)
} else if !os.IsNotExist(err) { } else if !os.IsNotExist(err) {
// If the keystore exists but cannot be accessed then return the error // If the keystore exists but cannot be accessed then return the error
return err return err
@@ -83,12 +107,10 @@ func (ks *KeyStore) Create() error {
mqmUID, mqmGID, err := command.LookupMQM() mqmUID, mqmGID, err := command.LookupMQM()
if err != nil { if err != nil {
log.Error(err)
return err return err
} }
err = os.Chown(ks.Filename, mqmUID, mqmGID) err = os.Chown(ks.Filename, mqmUID, mqmGID)
if err != nil { if err != nil {
log.Error(err)
return err return err
} }
return nil return nil
@@ -98,7 +120,6 @@ func (ks *KeyStore) Create() error {
func (ks *KeyStore) CreateStash() error { func (ks *KeyStore) CreateStash() error {
extension := filepath.Ext(ks.Filename) extension := filepath.Ext(ks.Filename)
stashFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".sth" stashFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".sth"
log.Debugf("TLS stash file: %v", stashFile)
_, err := os.Stat(stashFile) _, err := os.Stat(stashFile)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@@ -111,12 +132,10 @@ func (ks *KeyStore) CreateStash() error {
} }
mqmUID, mqmGID, err := command.LookupMQM() mqmUID, mqmGID, err := command.LookupMQM()
if err != nil { if err != nil {
log.Error(err)
return err return err
} }
err = os.Chown(stashFile, mqmUID, mqmGID) err = os.Chown(stashFile, mqmUID, mqmGID)
if err != nil { if err != nil {
log.Error(err)
return err return err
} }
return nil return nil
@@ -131,6 +150,33 @@ func (ks *KeyStore) Import(inputFile, password string) error {
return nil return nil
} }
// CreateSelfSignedCertificate creates a self-signed certificate in the keystore
func (ks *KeyStore) CreateSelfSignedCertificate(label, dn string) error {
out, _, err := command.Run(ks.command, "-cert", "-create", "-db", ks.Filename, "-pw", ks.Password, "-label", label, "-dn", dn)
if err != nil {
return fmt.Errorf("error running \"%v -cert -create\": %v %s", ks.command, err, out)
}
return nil
}
// Add adds a CA certificate to the keystore
func (ks *KeyStore) Add(inputFile, label string) error {
out, _, err := command.Run(ks.command, "-cert", "-add", "-db", ks.Filename, "-type", ks.keyStoreType, "-pw", ks.Password, "-file", inputFile, "-label", label)
if err != nil {
return fmt.Errorf("error running \"%v -cert -add\": %v %s", ks.command, err, out)
}
return nil
}
// Add adds a CA certificate to the keystore
func (ks *KeyStore) AddNoLabel(inputFile string) error {
out, _, err := command.Run(ks.command, "-cert", "-add", "-db", ks.Filename, "-type", ks.keyStoreType, "-pw", ks.Password, "-file", inputFile)
if err != nil {
return fmt.Errorf("error running \"%v -cert -add\": %v %s", ks.command, err, out)
}
return nil
}
// GetCertificateLabels returns the labels of all certificates in the key store // GetCertificateLabels returns the labels of all certificates in the key store
func (ks *KeyStore) GetCertificateLabels() ([]string, error) { func (ks *KeyStore) GetCertificateLabels() ([]string, error) {
out, _, err := command.Run(ks.command, "-cert", "-list", "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password) out, _, err := command.Run(ks.command, "-cert", "-list", "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password)
@@ -155,9 +201,42 @@ func (ks *KeyStore) GetCertificateLabels() ([]string, error) {
// RenameCertificate renames the specified certificate // RenameCertificate renames the specified certificate
func (ks *KeyStore) RenameCertificate(from, to string) error { func (ks *KeyStore) RenameCertificate(from, to string) error {
out, _, err := command.Run(ks.command, "-cert", "-rename", "-db", ks.Filename, "-pw", ks.Password, "-label", from, "-new_label", to) if ks.command == "/opt/mqm/bin/runmqakm" {
if err != nil { // runmqakm can't handle certs with ' in them so just use capicmd
return fmt.Errorf("error running \"%v -cert -rename\": %v %s", ks.command, err, out) cmd := exec.Command("/opt/mqm/gskit8/bin/gsk8capicmd_64", "-cert", "-rename", "-db", ks.Filename, "-pw", ks.Password, "-label", from, "-new_label", to)
cmd.Env = append(os.Environ(), "LD_LIBRARY_PATH=/opt/mqm/gskit8/lib64/:/opt/mqm/gskit8/lib")
out, _, err := command.RunCmd(cmd)
if err != nil {
return fmt.Errorf("error running \"%v -cert -rename\": %v %s", "/opt/mqm/gskit8/bin/gsk8capicmd_64", err, out)
}
} else {
out, _, err := command.Run(ks.command, "-cert", "-rename", "-db", ks.Filename, "-pw", ks.Password, "-label", from, "-new_label", to)
if err != nil {
return fmt.Errorf("error running \"%v -cert -rename\": %v %s", ks.command, err, out)
}
} }
return nil return nil
} }
// ListCertificates Lists all certificates in the keystore
func (ks *KeyStore) ListAllCertificates() ([]string, error) {
out, _, err := command.Run(ks.command, "-cert", "-list", "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password)
if err != nil {
return nil, fmt.Errorf("error running \"%v -cert -list\": %v %s", ks.command, err, out)
}
scanner := bufio.NewScanner(strings.NewReader(out))
var labels []string
for scanner.Scan() {
s := scanner.Text()
if strings.HasPrefix(s, "-") || strings.HasPrefix(s, "*-") || strings.HasPrefix(s, "!") {
s := strings.TrimLeft(s, "-*!")
labels = append(labels, strings.TrimSpace(s))
}
}
err = scanner.Err()
if err != nil {
return nil, err
}
return labels, nil
}

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -44,7 +44,7 @@ type Logger struct {
pid string pid string
serverName string serverName string
host string host string
user *user.User userName string
} }
// NewLogger creates a new logger // NewLogger creates a new logger
@@ -53,9 +53,13 @@ func NewLogger(writer io.Writer, debug bool, json bool, serverName string) (*Log
if err != nil { if err != nil {
return nil, err return nil, err
} }
// This can fail because the container's running as a random UID which
// is not known by the OS. We don't want this to break the logging
// entirely, so just use a blank user name.
user, err := user.Current() user, err := user.Current()
if err != nil { userName := ""
return nil, err if err == nil {
userName = user.Username
} }
return &Logger{ return &Logger{
mutex: sync.Mutex{}, mutex: sync.Mutex{},
@@ -66,7 +70,7 @@ func NewLogger(writer io.Writer, debug bool, json bool, serverName string) (*Log
pid: strconv.Itoa(os.Getpid()), pid: strconv.Itoa(os.Getpid()),
serverName: serverName, serverName: serverName,
host: hostname, host: hostname,
user: user, userName: userName,
}, nil }, nil
} }
@@ -93,7 +97,7 @@ func (l *Logger) log(level string, msg string) {
"ibm_serverName": l.serverName, "ibm_serverName": l.serverName,
"ibm_processName": l.processName, "ibm_processName": l.processName,
"ibm_processId": l.pid, "ibm_processId": l.pid,
"ibm_userName": l.user.Username, "ibm_userName": l.userName,
"type": "mq_containerlog", "type": "mq_containerlog",
} }
s, err := l.format(entry) s, err := l.format(entry)
@@ -110,11 +114,6 @@ func (l *Logger) log(level string, msg string) {
l.mutex.Unlock() l.mutex.Unlock()
} }
// LogDirect logs a message directly to stdout
func (l *Logger) LogDirect(msg string) {
fmt.Println(msg)
}
// Debug logs a line as debug // Debug logs a line as debug
func (l *Logger) Debug(args ...interface{}) { func (l *Logger) Debug(args ...interface{}) {
if l.debug { if l.debug {

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import (
"time" "time"
"github.com/ibm-messaging/mq-container/internal/logger" "github.com/ibm-messaging/mq-container/internal/logger"
"github.com/ibm-messaging/mq-container/internal/ready"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
@@ -39,12 +40,21 @@ var (
// GatherMetrics gathers metrics for the queue manager // GatherMetrics gathers metrics for the queue manager
func GatherMetrics(qmName string, log *logger.Logger) { func GatherMetrics(qmName string, log *logger.Logger) {
// If running in standby mode - wait until the queue manager becomes active
for {
active, _ := ready.IsRunningAsActiveQM(qmName)
if active {
break
}
time.Sleep(requestTimeout * time.Second)
}
metricsEnabled = true metricsEnabled = true
err := startMetricsGathering(qmName, log) err := startMetricsGathering(qmName, log)
if err != nil { if err != nil {
log.Errorf("Metrics Error: %s", err.Error()) log.Errorf("Metrics Error: %s", err.Error())
StopMetricsGathering() StopMetricsGathering(log)
} }
} }
@@ -76,6 +86,7 @@ func startMetricsGathering(qmName string, log *logger.Logger) error {
http.Handle("/metrics", prometheus.Handler()) http.Handle("/metrics", prometheus.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200) w.WriteHeader(200)
// #nosec G104
w.Write([]byte("Status: METRICS ACTIVE")) w.Write([]byte("Status: METRICS ACTIVE"))
}) })
@@ -83,7 +94,7 @@ func startMetricsGathering(qmName string, log *logger.Logger) error {
err = metricsServer.ListenAndServe() err = metricsServer.ListenAndServe()
if err != nil && err != http.ErrServerClosed { if err != nil && err != http.ErrServerClosed {
log.Errorf("Metrics Error: Failed to handle metrics request: %v", err) log.Errorf("Metrics Error: Failed to handle metrics request: %v", err)
StopMetricsGathering() StopMetricsGathering(log)
} }
}() }()
@@ -91,7 +102,7 @@ func startMetricsGathering(qmName string, log *logger.Logger) error {
} }
// StopMetricsGathering stops gathering metrics for the queue manager // StopMetricsGathering stops gathering metrics for the queue manager
func StopMetricsGathering() { func StopMetricsGathering(log *logger.Logger) {
if metricsEnabled { if metricsEnabled {
@@ -101,6 +112,9 @@ func StopMetricsGathering() {
// Shutdown HTTP server // Shutdown HTTP server
timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second) timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
metricsServer.Shutdown(timeout) err := metricsServer.Shutdown(timeout)
if err != nil {
log.Errorf("Failed to shutdown metrics server: %v", err)
}
} }
} }

View File

@@ -62,6 +62,7 @@ func processMetrics(log *logger.Logger, qmName string) {
firstConnect = false firstConnect = false
startChannel <- true startChannel <- true
} }
// #nosec G104
metrics, _ = initialiseMetrics(log) metrics, _ = initialiseMetrics(log)
} }

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -71,5 +71,8 @@ func GetQueueManager(name string) (*QueueManager, error) {
// GetErrorLogDirectory returns the directory holding the error logs for the // GetErrorLogDirectory returns the directory holding the error logs for the
// specified queue manager // specified queue manager
func GetErrorLogDirectory(qm *QueueManager) string { func GetErrorLogDirectory(qm *QueueManager) string {
if qm.DataPath != "" {
return filepath.Join(qm.DataPath, "errors")
}
return filepath.Join(qm.Prefix, "qmgrs", qm.Directory, "errors") return filepath.Join(qm.Prefix, "qmgrs", qm.Directory, "errors")
} }

View File

@@ -0,0 +1,264 @@
/*
© Copyright IBM Corporation 2019
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 mqscredact
import (
"bufio"
"io"
"regexp"
"strings"
)
/* List of sensitive MQ Parameters */
var sensitiveParameters = []string{"LDAPPWD", "PASSWORD", "SSLCRYP"}
// redactionString is what sensitive paramters will be replaced with
const redactionString = "(*********)"
func findEndOfParamterString(stringDenoter rune, r *bufio.Reader) string {
parameter := ""
for {
char, _, err := r.ReadRune()
if err != nil {
return parameter
}
parameter = parameter + string(char)
if char == stringDenoter {
break
} else if char == '\n' {
// Check if we're on a comment line
NewLineLoop:
for {
// Look at next character without moving buffer forwards
chars, err := r.Peek(1)
if err != nil {
return parameter
}
// Check if we're at the beginning of some data.
startOutput, _ := regexp.MatchString(`[^:0-9\s]`, string(chars[0]))
if startOutput {
// We are at the start, check if we're on a comment line
if chars[0] == '*' {
// found a comment line. go to the next newline chraracter
CommentLoop:
for {
char, _, err = r.ReadRune()
if err != nil {
return parameter
}
parameter = parameter + string(char)
if char == '\n' {
break CommentLoop
}
}
// Go round again as we're now on a new line
continue NewLineLoop
}
// We've checked for comment and it isn't a comment line so break without moving buffer forwards
break NewLineLoop
}
// Move the buffer forward and try again
char, _, _ = r.ReadRune()
parameter = parameter + string(char)
}
}
}
return parameter
}
// getParameterString reads from r in order to find the end of the MQSC Parameter value. This is enclosed in ( ).
// This function will return what it finds and will increment the reader pointer along as it goes.
func getParameterString(r *bufio.Reader) string {
// Add the ( in as it will have been dropped before.
parameter := "("
Loop:
for {
char, _, err := r.ReadRune()
if err != nil {
return parameter
}
parameter = parameter + string(char)
switch char {
case ')':
break Loop
// TODO: Duplicate code..
case '\'', '"':
parameter = parameter + findEndOfParamterString(char, r)
}
}
return parameter
}
func resetAllParameters(currentVerb, originalString *string, lineContinuation, foundGap, parameterNext, redacting, checkComment *bool) {
*currentVerb = ""
*originalString = ""
*lineContinuation = false
*foundGap = false
*parameterNext = false
*redacting = false
*checkComment = true
}
// Redact is the main function for redacting sensitive parameters in MQSC strings
// It accepts a string and redacts sensitive paramters such as LDAPPWD or PASSWORD
func Redact(out string) (string, error) {
out = strings.TrimSpace(out)
var returnStr, currentVerb, originalString string
var lineContinuation, foundGap, parameterNext, redacting, checkComment bool
newline := true
resetAllParameters(&currentVerb, &originalString, &lineContinuation, &foundGap, &parameterNext, &redacting, &checkComment)
r := bufio.NewReader(strings.NewReader(out))
MainLoop:
for {
// We have found a opening ( so use special parameter parsing
if parameterNext {
parameterStr := getParameterString(r)
if !redacting {
returnStr = returnStr + parameterStr
} else {
returnStr = returnStr + redactionString
}
resetAllParameters(&currentVerb, &originalString, &lineContinuation, &foundGap, &parameterNext, &redacting, &checkComment)
}
// Loop round getting hte next parameter
char, _, err := r.ReadRune()
if err == io.EOF {
if originalString != "" {
returnStr = returnStr + originalString
}
break
} else if err != nil {
return returnStr, err
}
/* We need to push forward until we find a non-whitespace, digit or colon character */
if newline {
startOutput, _ := regexp.MatchString(`[^:0-9\s]`, string(char))
if !startOutput {
originalString = originalString + string(char)
continue MainLoop
}
newline = false
}
switch char {
// Found a line continuation character
case '+', '-':
lineContinuation = true
foundGap = false
originalString = originalString + string(char)
continue MainLoop
// Found whitespace/new line
case '\n':
checkComment = true
newline = true
fallthrough
case '\t', '\r', ' ':
if !lineContinuation {
foundGap = true
}
originalString = originalString + string(char)
continue MainLoop
// Found a paramter value
case '(':
parameterNext = true
/* Do not continue as we need to do some checks */
// Found a comment, parse in a special manner
case '*':
if checkComment {
originalString = originalString + string(char)
// Loop round until we find the new line character that marks the end of the comment
CommentLoop:
for {
char, _, err := r.ReadRune()
if err == io.EOF {
if originalString != "" {
returnStr = returnStr + originalString
}
break MainLoop
} else if err != nil {
return returnStr, err
}
originalString = originalString + string(char)
if char == '\n' {
break CommentLoop
}
}
//Comment has been read and added to original string, go back to start
checkComment = true
newline = true
continue MainLoop
}
/* Do not continue as we need to do some checks */
} //end of switch
checkComment = false
if lineContinuation {
lineContinuation = false
}
if foundGap || parameterNext {
// we've completed an parameter so check whether it is sensitive
currentVerb = strings.ToUpper(currentVerb)
if isSensitiveCommand(currentVerb) {
redacting = true
}
// Add the unedited string to the return string
returnStr = returnStr + originalString
//reset some of the parameters
originalString = ""
currentVerb = ""
foundGap = false
lineContinuation = false
}
originalString = originalString + string(char)
currentVerb = currentVerb + string(char)
}
return returnStr, nil
}
// isSensitiveCommand checks whether the given string contains a sensitive parameter.
// We use contains here because we can't determine whether a line continuation seperates
// parts of a parameter or two different parameters.
func isSensitiveCommand(command string) bool {
for _, v := range sensitiveParameters {
if strings.Contains(command, v) {
return true
}
}
return false
}

View File

@@ -0,0 +1,171 @@
/*
© Copyright IBM Corporation 2019
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 mqscredact
import (
"strings"
"testing"
)
const passwordString = passwordHalf1 + passwordHalf2
const passwordHalf1 = "hippo"
const passwordHalf2 = "123456"
var testStrings = [...]string{
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('" + passwordString + "')",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordString + "\")",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD ('" + passwordString + "')",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD\t\t('" + passwordString + "')",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) ldappwd('" + passwordString + "')",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LdApPwD('" + passwordString + "')",
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD('" + passwordString + "')",
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD(\"" + passwordString + "\")",
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD ('" + passwordString + "')",
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD\t\t('" + passwordString + "')",
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) password('" + passwordString + "')",
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) pAsSwOrD('" + passwordString + "')",
"ALTER QMGR SSLCRYP('" + passwordString + "')",
"ALTER QMGR SSLCRYP(\"" + passwordString + "\")",
"ALTER QMGR SSLCRYP ('" + passwordString + "')",
"ALTER QMGR SSLCRYP\t\t('" + passwordString + "')",
"ALTER QMGR sslcryp('" + passwordString + "')",
"ALTER QMGR sslCRYP('" + passwordString + "')",
// Line continuation ones
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "+\n " + passwordHalf2 + "\")",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "+\n\t" + passwordHalf2 + "\")",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "+\n\t " + passwordHalf2 + "\")",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('" + passwordHalf1 + "+\n " + passwordHalf2 + "')",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('" + passwordHalf1 + "+\n\t" + passwordHalf2 + "')",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('" + passwordHalf1 + "+\n\t " + passwordHalf2 + "')",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "-\n" + passwordHalf2 + "\")",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('" + passwordHalf1 + "-\n" + passwordHalf2 + "')",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "+ \n " + passwordHalf2 + "\")",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "+\t\n " + passwordHalf2 + "\")",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "- \n" + passwordHalf2 + "\")",
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "-\t\n" + passwordHalf2 + "\")",
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD(\"" + passwordHalf1 + "+\n " + passwordHalf2 + "\")",
"ALTER QMGR SSLCRYP(\"" + passwordHalf1 + "+\n " + passwordHalf2 + "\")",
//edge cases
"ALTER QMGR SSLCRYP(\"" + passwordHalf1 + "+\n 123+\n 456\")",
"ALTER QMGR SSLCRYP(\"" + passwordHalf1 + "-\n123-\n456\")",
"ALTER QMGR SSLCRYP(\"" + passwordHalf1 + "+\n 1+\n 2+\n 3+\n 4+\n 5+\n 6\")",
"ALTER QMGR SSLCRYP(\"" + passwordHalf1 + "-\n1-\n2-\n3-\n4-\n5-\n6\")",
"ALTER QMGR SSLCRYP + \n (\"" + passwordHalf1 + "+\n 1+\n 2+\n 3+\n 4+\n 5+\n 6\")",
"ALTER QMGR SSLCRYP - \n(\"" + passwordHalf1 + "-\n1-\n2-\n3-\n4-\n5-\n6\")",
"ALTER QMGR SSL + \n CRYP(\"" + passwordHalf1 + "+\n 1+\n 2+\n 3+\n 4+\n 5+\n 6\")",
"ALTER QMGR SSL - \nCRYP(\"" + passwordHalf1 + "-\n1-\n2-\n3-\n4-\n5-\n6\")",
"ALTER QMGR + \n SSL +\n CRYP(\"" + passwordHalf1 + "+\n 1+\n 2+\n 3+\n 4+\n 5+\n 6\") +\n TEST(1234)",
"ALTER QMGR -\nSSL -\nCRYP(\"" + passwordHalf1 + "-\n1-\n2-\n3-\n4-\n5-\n6\") -\nTEST(1234)",
"ALTER QMGR +\n * COMMENT\n SSL +\n * COMMENT IN MIDDLE\n CRYP('" + passwordString + "')",
" 1: ALTER CHANNEL(TEST2) CHLTYPE(SDR) PASS+\n : *test comment\n : WORD('" + passwordString + "')",
" 2: ALTER CHANNEL(TEST3) CHLTYPE(SDR) PASSWORD('" + passwordHalf1 + "-\n*commentinmiddle with ' \n" + passwordHalf2 + "')",
" 3: ALTER CHANNEL(TEST3) CHLTYPE(SDR) PASSWORD('" + passwordHalf1 + "-\n*commentinmiddle with ') \n" + passwordHalf2 + "')",
}
var expected = [...]string{
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD " + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD\t\t" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) ldappwd" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LdApPwD" + redactionString,
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD" + redactionString,
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD" + redactionString,
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD " + redactionString,
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD\t\t" + redactionString,
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) password" + redactionString,
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) pAsSwOrD" + redactionString,
"ALTER QMGR SSLCRYP" + redactionString,
"ALTER QMGR SSLCRYP" + redactionString,
"ALTER QMGR SSLCRYP " + redactionString,
"ALTER QMGR SSLCRYP\t\t" + redactionString,
"ALTER QMGR sslcryp" + redactionString,
"ALTER QMGR sslCRYP" + redactionString,
// Line continuation ones
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD" + redactionString,
"ALTER QMGR SSLCRYP" + redactionString,
//edge cases
"ALTER QMGR SSLCRYP" + redactionString,
"ALTER QMGR SSLCRYP" + redactionString,
"ALTER QMGR SSLCRYP" + redactionString,
"ALTER QMGR SSLCRYP" + redactionString,
"ALTER QMGR SSLCRYP + \n \t " + redactionString,
"ALTER QMGR SSLCRYP - \n " + redactionString,
"ALTER QMGR SSL + \n CRYP" + redactionString,
"ALTER QMGR SSL - \nCRYP" + redactionString,
"ALTER QMGR + \n SSL +\n CRYP" + redactionString + " +\n TEST(1234)",
"ALTER QMGR -\nSSL -\nCRYP" + redactionString + " -\nTEST(1234)",
"ALTER QMGR +\n * COMMENT\n SSL +\n * COMMENT IN MIDDLE\n CRYP" + redactionString,
"1: ALTER CHANNEL(TEST2) CHLTYPE(SDR) PASS+\n : *test comment\n : WORD" + redactionString,
"2: ALTER CHANNEL(TEST3) CHLTYPE(SDR) PASSWORD" + redactionString,
"3: ALTER CHANNEL(TEST3) CHLTYPE(SDR) PASSWORD" + redactionString,
}
// Returns true if the 2 strings are equal ignoring whitespace characters
func compareIgnoreWhiteSpace(str1, str2 string) bool {
whiteSpaces := [...]string{" ", "\t", "\n", "\r"}
for _, w := range whiteSpaces {
str1 = strings.Replace(str1, w, "", -1)
str2 = strings.Replace(str2, w, "", -1)
}
return str1 == str2
}
func TestAll(t *testing.T) {
for i, v := range testStrings {
back, _ := Redact(v)
if strings.Contains(back, passwordHalf1) || strings.Contains(back, passwordHalf2) || strings.Contains(back, passwordString) {
t.Errorf("MAJOR FAIL[%d]: Found an instance of the password. ", i)
}
if !compareIgnoreWhiteSpace(back, expected[i]) {
t.Errorf("FAIL[%d]:\nGave :%s\nexpected:%s\ngot :%s", i, v, expected[i], back)
}
}
}

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,7 +13,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package main
// Package mqtemplate contains code to process template files
package mqtemplate
import ( import (
"os" "os"
@@ -21,11 +23,12 @@ import (
"text/template" "text/template"
"github.com/ibm-messaging/mq-container/internal/command" "github.com/ibm-messaging/mq-container/internal/command"
"github.com/ibm-messaging/mq-container/internal/logger"
) )
// processTemplateFile takes a Go templateFile, and processes it with the // ProcessTemplateFile takes a Go templateFile, and processes it with the
// supplied data, writing to destFile // supplied data, writing to destFile
func processTemplateFile(templateFile, destFile string, data interface{}) error { func ProcessTemplateFile(templateFile, destFile string, data interface{}, log *logger.Logger) error {
// Re-configure channel if app password not set // Re-configure channel if app password not set
t, err := template.ParseFiles(templateFile) t, err := template.ParseFiles(templateFile)
if err != nil { if err != nil {
@@ -36,7 +39,12 @@ func processTemplateFile(templateFile, destFile string, data interface{}) error
_, err = os.Stat(dir) _, err = os.Stat(dir)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
os.MkdirAll(dir, 0660) // #nosec G301
err = os.MkdirAll(dir, 0770)
if err != nil {
log.Error(err)
return err
}
mqmUID, mqmGID, err := command.LookupMQM() mqmUID, mqmGID, err := command.LookupMQM()
if err != nil { if err != nil {
log.Error(err) log.Error(err)
@@ -51,6 +59,7 @@ func processTemplateFile(templateFile, destFile string, data interface{}) error
return err return err
} }
} }
// #nosec G302
f, err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, 0660) f, err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, 0660)
defer f.Close() defer f.Close()
err = t.Execute(f, data) err = t.Execute(f, data)

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -20,6 +20,9 @@ package ready
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
"strings"
"github.com/ibm-messaging/mq-container/internal/command"
) )
const fileName string = "/run/runmqserver/ready" const fileName string = "/run/runmqserver/ready"
@@ -62,3 +65,24 @@ func Check() (bool, error) {
} }
return exists, nil return exists, nil
} }
// IsRunningAsActiveQM returns true if the queue manager is running in active mode
func IsRunningAsActiveQM(name string) (bool, error) {
return isRunningQM(name, "(RUNNING)")
}
// IsRunningAsStandbyQM returns true if the queue manager is running in standby mode
func IsRunningAsStandbyQM(name string) (bool, error) {
return isRunningQM(name, "(RUNNING AS STANDBY)")
}
func isRunningQM(name string, status string) (bool, error) {
out, _, err := command.Run("dspmq", "-n", "-m", name)
if err != nil {
return false, err
}
if strings.Contains(string(out), status) {
return true, nil
}
return false, nil
}

610
internal/tls/tls.go Normal file
View File

@@ -0,0 +1,610 @@
/*
© Copyright IBM Corporation 2019
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 tls
import (
"bufio"
"fmt"
"io/ioutil"
pwr "math/rand"
"os"
"path/filepath"
"strings"
"time"
"crypto/rand"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"github.com/ibm-messaging/mq-container/internal/filecheck"
"github.com/ibm-messaging/mq-container/internal/keystore"
pkcs "software.sslmate.com/src/go-pkcs12"
)
// IntegrationDefaultLabel is the default certificate label used by Cloud Integration Platform
const IntegrationDefaultLabel = "default"
// P12TrustStoreName is the name of the PKCS#12 truststore used by the webconsole
const P12TrustStoreName = "trust.p12"
// CMSKeyStoreName is the name of the CMS Keystore used by the queue manager
const CMSKeyStoreName = "key.kdb"
type KeyStoreData struct {
Keystore *keystore.KeyStore
Password string
TrustedCerts []*pem.Block
KnownFingerPrints []string
KeyLabels []string
}
type P12KeyFiles struct {
Keystores []string
Password string
}
func getCertFingerPrint(block *pem.Block) (string, error) {
// Add to future truststore and known certs (if not already there)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return "", fmt.Errorf("could not parse x509 certificate: %v", err)
}
sha512Sum := sha512.Sum512(cert.Raw)
sha512str := string(sha512Sum[:])
return sha512str, nil
}
// Add to Keystores known certs (if not already there) and add to the
// Keystore if "addToKeystore" is true.
func addCertToKeyData(block *pem.Block, keyData *KeyStoreData, addToKeystore bool) error {
sha512str, err := getCertFingerPrint(block)
if err != nil {
return err
}
known := false
for _, fingerprint := range keyData.KnownFingerPrints {
if fingerprint == sha512str {
known = true
break
}
}
if !known {
// Sometimes we don't want to add to the keystore trust here.
// For example if it will be imported with the key later.
if addToKeystore {
keyData.TrustedCerts = append(keyData.TrustedCerts, block)
}
keyData.KnownFingerPrints = append(keyData.KnownFingerPrints, sha512str)
}
return nil
}
// Generates a random 12 character password from the characters a-z, A-Z, 0-9.
func generateRandomPassword() string {
pwr.Seed(time.Now().Unix())
validChars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
validcharArray := []byte(validChars)
password := ""
for i := 0; i < 12; i++ {
password = password + string(validcharArray[pwr.Intn(len(validcharArray))])
}
return password
}
// Creates the PKCS#12 Truststore and the CMS Keystore.
func generateAllStores(dir string) (KeyStoreData, KeyStoreData, error) {
var cmsKeystore, p12TrustStore KeyStoreData
pw := generateRandomPassword()
cmsKeystore.Password = pw
p12TrustStore.Password = pw
// Create the keystore Directory (if it doesn't already exist)
// #nosec G301 - write group permissions are required
err := os.MkdirAll(dir, 0770)
if err != nil {
return cmsKeystore, p12TrustStore, fmt.Errorf("Failed to create keystore directory: %v", err)
}
p12TrustStore.Keystore = keystore.NewPKCS12KeyStore(filepath.Join(dir, P12TrustStoreName), p12TrustStore.Password)
err = p12TrustStore.Keystore.Create()
if err != nil {
return cmsKeystore, p12TrustStore, fmt.Errorf("Failed to create PKCS#12 TrustStore: %v", err)
}
cmsKeystore.Keystore = keystore.NewCMSKeyStore(filepath.Join(dir, CMSKeyStoreName), cmsKeystore.Password)
err = cmsKeystore.Keystore.Create()
if err != nil {
return cmsKeystore, p12TrustStore, fmt.Errorf("Failed to create CMS KeyStore: %v", err)
}
return cmsKeystore, p12TrustStore, nil
}
// processKeys walks through the keyDir directory and imports any keys it finds to individual PKCS#12 keystores
// and the CMS KeyStore. The label it uses is the name of the directory if finds the keys in.
func processKeys(keyDir, outputDir string, cmsKeyDB, p12TrustDB *KeyStoreData) (string, P12KeyFiles, error) {
var p12s P12KeyFiles
var firstLabel string
pwToUse := cmsKeyDB.Password
p12s.Password = pwToUse
trustStoreReserveredName := P12TrustStoreName[0 : len(P12TrustStoreName)-len(filepath.Ext(P12TrustStoreName))]
keyList, err := ioutil.ReadDir(keyDir)
if err == nil && len(keyList) > 0 {
// Found some keys, verify the contents
for _, key := range keyList {
keys, _ := ioutil.ReadDir(filepath.Join(keyDir, key.Name()))
keyLabel := key.Name()
if keyLabel == trustStoreReserveredName {
return firstLabel, p12s, fmt.Errorf("Found key with same label set as same name as truststore(%s). This is not allowed", trustStoreReserveredName)
}
keyfilename := ""
var keyfile interface{}
var certFile *x509.Certificate
var caFile []*x509.Certificate
// find the keyfile name
for _, a := range keys {
if strings.HasSuffix(a.Name(), ".key") {
// #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant
keyFile, err := ioutil.ReadFile(filepath.Join(keyDir, key.Name(), a.Name()))
if err != nil {
return firstLabel, p12s, fmt.Errorf("Could not read keyfile %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err)
}
block, _ := pem.Decode(keyFile)
if block == nil {
return firstLabel, p12s, fmt.Errorf("Could not decode keyfile %s: pem.Decode returned nil", filepath.Join(keyDir, key.Name(), a.Name()))
}
//Test whether it is PKCS1
keyfile, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
// Before we fail check whether it is PKCS8
keyfile, err = x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
fmt.Printf("key %s ParsePKCS1/8PrivateKey ERR: %v\n", filepath.Join(keyDir, key.Name(), a.Name()), err)
return firstLabel, p12s, err
}
//It was PKCS8 afterall
}
keyfilename = a.Name()
}
}
if keyfile == nil {
continue
}
// Find out what the keyfile was called without the extension
prefix := keyfilename[0 : len(keyfilename)-len(filepath.Ext(keyfilename))]
for _, a := range keys {
if strings.HasSuffix(a.Name(), ".key") {
continue
}
if strings.HasPrefix(a.Name(), prefix) && strings.HasSuffix(a.Name(), ".crt") {
// #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant
cert, err := ioutil.ReadFile(filepath.Join(keyDir, key.Name(), a.Name()))
if err != nil {
return firstLabel, p12s, fmt.Errorf("Could not read file %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err)
}
block, _ := pem.Decode(cert)
if block == nil {
return firstLabel, p12s, fmt.Errorf("Could not decode certificate %s: pem.Decode returned nil", filepath.Join(keyDir, key.Name(), a.Name()))
}
certFile, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return firstLabel, p12s, fmt.Errorf("Could not parse certificate %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err)
}
// Add to the dup list for the CMS keystore but not the PKCS#12 Truststore
err = addCertToKeyData(block, cmsKeyDB, false)
} else if strings.HasSuffix(a.Name(), ".crt") {
// #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant
remainder, err := ioutil.ReadFile(filepath.Join(keyDir, key.Name(), a.Name()))
if err != nil {
return firstLabel, p12s, fmt.Errorf("Could not read file %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err)
}
for string(remainder) != "" {
var block *pem.Block
block, remainder = pem.Decode(remainder)
// If we can't decode the CA certificate then just exit.
if block == nil {
break
}
// Add to the dup list for the CMS keystore
err = addCertToKeyData(block, cmsKeyDB, false)
// Add to the p12 truststore
err = addCertToKeyData(block, p12TrustDB, true)
caCert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return firstLabel, p12s, fmt.Errorf("Could not parse CA certificate %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err)
}
caFile = append(caFile, caCert)
}
}
}
// Create p12 keystore
file, err := pkcs.Encode(rand.Reader, keyfile, certFile, caFile, pwToUse)
if err != nil {
return firstLabel, p12s, fmt.Errorf("Could not encode PKCS#12 Keystore %s: %v", keyLabel+".p12", err)
}
err = ioutil.WriteFile(filepath.Join(outputDir, keyLabel+".p12"), file, 0644)
if err != nil {
return firstLabel, p12s, fmt.Errorf("Could not write PKCS#12 Keystore %s: %v", filepath.Join(outputDir, keyLabel+".p12"), err)
}
p12s.Keystores = append(p12s.Keystores, keyLabel+".p12")
// Add to the CMS keystore
err = cmsKeyDB.Keystore.Import(filepath.Join(outputDir, keyLabel+".p12"), pwToUse)
if err != nil {
return firstLabel, p12s, fmt.Errorf("Could not import keys from %s into CMS Keystore: %v", filepath.Join(outputDir, keyLabel+".p12"), err)
}
// Relabel it
allLabels, err := cmsKeyDB.Keystore.GetCertificateLabels()
if err != nil {
return firstLabel, p12s, fmt.Errorf("Could not list keys in CMS Keystore: %v", err)
}
relabelled := false
for _, cl := range allLabels {
found := false
for _, kl := range cmsKeyDB.KeyLabels {
if strings.Trim(cl, "\"") == kl {
found = true
break
}
}
if !found {
// This is the one to rename
err = cmsKeyDB.Keystore.RenameCertificate(strings.Trim(cl, "\""), keyLabel)
if err != nil {
return firstLabel, p12s, err
}
relabelled = true
cmsKeyDB.KeyLabels = append(cmsKeyDB.KeyLabels, keyLabel)
break
}
}
if !relabelled {
return firstLabel, p12s, fmt.Errorf("Unable to find the added key for %s in CMS keystore", keyLabel)
}
// First key found so mark it as the one to use with the queue manager.
if firstLabel == "" {
firstLabel = keyLabel
}
}
}
return firstLabel, p12s, nil
}
// processTrustCertificates walks through the trustDir directory and adds any certificates it finds
// to the PKCS#12 Truststore and the CMS KeyStore as long as has not already been added.
func processTrustCertificates(trustDir string, cmsKeyDB, p12TrustDB *KeyStoreData) error {
certList, err := ioutil.ReadDir(trustDir)
if err == nil && len(certList) > 0 {
// Found some keys, verify the contents
for _, cert := range certList {
certs, _ := ioutil.ReadDir(filepath.Join(trustDir, cert.Name()))
for _, a := range certs {
if strings.HasSuffix(a.Name(), ".crt") {
// #nosec G304 - filename variable is derived from contents of 'trustDir' which is a defined constant
remainder, err := ioutil.ReadFile(filepath.Join(trustDir, cert.Name(), a.Name()))
if err != nil {
return fmt.Errorf("Could not read file %s: %v", filepath.Join(trustDir, cert.Name(), a.Name()), err)
}
for string(remainder) != "" {
var block *pem.Block
block, remainder = pem.Decode(remainder)
if block == nil {
break
}
// Add to the CMS keystore
err = addCertToKeyData(block, cmsKeyDB, true)
// Add to the p12 truststore
err = addCertToKeyData(block, p12TrustDB, true)
}
}
}
}
}
// We've potentially created two lists of certificates to import. Add them both to relevant Truststores
if len(p12TrustDB.TrustedCerts) > 0 {
// Do P12 TrustStore first
temporaryPemFile := filepath.Join("/tmp", "trust.pem")
_, err := os.Stat(temporaryPemFile)
if err == nil {
err = os.Remove(temporaryPemFile)
if err != nil {
return fmt.Errorf("Could not remove file %v: %v", temporaryPemFile, err)
}
}
err = writeCertsToFile(temporaryPemFile, p12TrustDB.TrustedCerts)
if err != nil {
return err
}
err = p12TrustDB.Keystore.AddNoLabel(temporaryPemFile)
if err != nil {
return fmt.Errorf("Could not add certificates to PKCS#12 Truststore: %v", err)
}
// We need to relabel everything because liberty doesn't play nicely with autolabelled certs
allCerts, err := p12TrustDB.Keystore.ListAllCertificates()
if err != nil || len(allCerts) <= 0 {
return fmt.Errorf("Could not get any certificates from PKCS#12 Truststore: %v", err)
}
for i, cert := range allCerts {
cert = strings.Trim(cert, "\"")
cert = strings.TrimSpace(cert)
newLabel := fmt.Sprintf("Trust%d", i)
err = p12TrustDB.Keystore.RenameCertificate(cert, newLabel)
if err != nil || len(allCerts) <= 0 {
return fmt.Errorf("Could not rename certificate %s to %s in PKCS#12 Truststore: %v", cert, newLabel, err)
}
}
}
if len(cmsKeyDB.TrustedCerts) > 0 {
// Now the CMS Keystore
temporaryPemFile := filepath.Join("/tmp", "cmsTrust.pem")
_, err := os.Stat(temporaryPemFile)
if err == nil {
err = os.Remove(temporaryPemFile)
if err != nil {
return fmt.Errorf("Could not remove file %v: %v", temporaryPemFile, err)
}
}
err = writeCertsToFile(temporaryPemFile, cmsKeyDB.TrustedCerts)
if err != nil {
return err
}
err = cmsKeyDB.Keystore.AddNoLabel(temporaryPemFile)
if err != nil {
return fmt.Errorf("Could not add certificates to CMS keystore: %v", err)
}
}
return nil
}
// Writes a given list of certificates to a file.
func writeCertsToFile(file string, certs []*pem.Block) error {
f, err := os.Create(file)
if err != nil {
return fmt.Errorf("writeCertsToFile: Could not create file %s: %v", file, err)
}
defer f.Close()
w := bufio.NewWriter(f)
for i, c := range certs {
err := pem.Encode(w, c)
if err != nil {
return fmt.Errorf("writeCertsToFile: Could not encode certificate number %d: %v", i, err)
}
err = w.Flush()
if err != nil {
return fmt.Errorf("writeCertsToFile: Failed to write to file %s: %v", file, err)
}
}
return nil
}
// ConfigureTLSKeystores sets up the TLS Trust and Keystores for use
func ConfigureTLSKeystores(keyDir, certDir, outputDir string) (string, KeyStoreData, KeyStoreData, P12KeyFiles, error) {
var returnLabel, label string
var cmsKeyDB, p12TrustDB KeyStoreData
var keyFiles P12KeyFiles
var err error
cmsKeyDB, p12TrustDB, err = generateAllStores(outputDir)
if err != nil {
return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err
}
returnLabel, err = expandOldTLSVariable(keyDir, outputDir, &cmsKeyDB, &p12TrustDB)
if err != nil {
return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err
}
label, keyFiles, err = processKeys(keyDir, outputDir, &cmsKeyDB, &p12TrustDB)
if err != nil {
return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err
}
if returnLabel == "" {
returnLabel = label
}
err = processTrustCertificates(certDir, &cmsKeyDB, &p12TrustDB)
if err != nil {
return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err
}
return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err
}
// This function supports the old mechanism of importing certificates supplied by the MQ_TLS_KEYSTORE envvar
func expandOldTLSVariable(keyDir, outputDir string, cmsKeyDB, p12TrustDB *KeyStoreData) (string, error) {
// TODO: Change this or find a way to set it
outputDirName := "acopiedcertificate"
// Check whether the old variable is set. If not exit quietly
keyfile := os.Getenv("MQ_TLS_KEYSTORE")
if keyfile == "" {
return "", nil
}
// There is a file to read and process
keyfilepw := os.Getenv("MQ_TLS_PASSPHRASE")
if !strings.HasSuffix(keyfile, ".p12") {
return "", fmt.Errorf("MQ_TLS_KEYSTORE (%s) does not point to a PKCS#12 file ending with the suffix .p12", keyfile)
}
_, err := os.Stat(keyfile)
if err != nil {
return "", fmt.Errorf("File %s referenced by MQ_TLS_KEYSTORE does not exist", keyfile)
}
err = filecheck.CheckFileSource(keyfile)
if err != nil {
return "", fmt.Errorf("File %s referenced by MQ_TLS_KEYSTORE is invalid: %v", keyfile, err)
}
// #nosec G304 - filename variable 'keyfile' is checked above to ensure it is valid
readkey, err := ioutil.ReadFile(keyfile)
if err != nil {
return "", fmt.Errorf("Failed to read %s: %v", keyfile, err)
}
// File has been checked and read, decode it.
pk, cert, cas, err := pkcs.DecodeChain(readkey, keyfilepw)
if err != nil {
return "", fmt.Errorf("Failed to decode %s: %v", keyfile, err)
}
// Find a directory name that doesn't exist
for {
_, err := os.Stat(filepath.Join(keyDir, outputDirName))
if err == nil {
outputDirName = outputDirName + "0"
} else {
break
}
}
//Bceause they supplied this certificate using the old method we should use this for qm & webconsole
overrideLabel := outputDirName
// Write out the certificate for the private key
if cert != nil {
block := pem.Block{
Type: "CERTIFICATE",
Headers: nil,
Bytes: cert.Raw,
}
err = addCertToKeyData(&block, cmsKeyDB, false)
if err != nil {
return "", fmt.Errorf("expandOldTLSVariable: Failed to add cert to CMS Keystore duplicate list: %v", err)
}
err = addCertToKeyData(&block, p12TrustDB, true)
if err != nil {
return "", fmt.Errorf("expandOldTLSVariable: Failed to add cert to P12 Truststore duplicate list: %v", err)
}
}
// now write out all the ca certificates
if cas != nil || len(cas) > 0 {
for i, c := range cas {
block := pem.Block{
Type: "CERTIFICATE",
Headers: nil,
Bytes: c.Raw,
}
// Add to the dup list for the CMS keystore
err = addCertToKeyData(&block, cmsKeyDB, false)
if err != nil {
return "", fmt.Errorf("expandOldTLSVariable: Failed to add CA cert %d to CMS Keystore duplicate list: %v", i, err)
}
// Add to the p12 truststore
err = addCertToKeyData(&block, p12TrustDB, true)
if err != nil {
return "", fmt.Errorf("expandOldTLSVariable: Failed to add CA cert %d to P12 Truststore duplicate list: %v", i, err)
}
}
}
// Now we've handled the certificates copy the keystore into place
destination := filepath.Join(outputDir, outputDirName+".p12")
// Create p12 keystore
file, err := pkcs.Encode(rand.Reader, pk, cert, cas, p12TrustDB.Password)
if err != nil {
return "", fmt.Errorf("Failed to re-encode p12 keystore: %v", err)
}
err = ioutil.WriteFile(destination, file, 0644)
if err != nil {
return "", fmt.Errorf("Failed to write p12 keystore: %v", err)
}
// Add to the CMS keystore
err = cmsKeyDB.Keystore.Import(destination, p12TrustDB.Password)
if err != nil {
return "", fmt.Errorf("Failed to import p12 keystore %s: %v", destination, err)
}
if pk != nil {
// Relabel the key
allLabels, err := cmsKeyDB.Keystore.GetCertificateLabels()
if err != nil {
fmt.Printf("cms GetCertificateLabels: %v\n", err)
return "", err
}
relabelled := false
for _, cl := range allLabels {
found := false
for _, kl := range cmsKeyDB.KeyLabels {
if strings.Trim(cl, "\"") == kl {
found = true
break
}
}
if !found {
// This is the one to rename
err = cmsKeyDB.Keystore.RenameCertificate(strings.Trim(cl, "\""), outputDirName)
if err != nil {
return "", err
}
relabelled = true
cmsKeyDB.KeyLabels = append(cmsKeyDB.KeyLabels, outputDirName)
break
}
}
if !relabelled {
return "", fmt.Errorf("Unable to find the added key in CMS keystore")
}
}
return overrideLabel, nil
}

81
internal/user/user.go Normal file
View File

@@ -0,0 +1,81 @@
/*
© Copyright IBM Corporation 2018, 2019
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 user
import (
"fmt"
"os/user"
"strings"
"github.com/ibm-messaging/mq-container/internal/command"
)
// User holds information on primary and supplemental OS groups
type User struct {
UID string
Name string
PrimaryGID string
SupplementalGID []string
}
// GetUser returns the current user and group information
func GetUser() (User, error) {
u, err := user.Current()
if err != nil {
return User{}, err
}
g, err := getCurrentUserGroups()
if err != nil {
return User{}, err
}
if err != nil && len(g) == 0 {
return User{
UID: u.Uid,
Name: u.Name,
PrimaryGID: u.Gid,
SupplementalGID: []string{},
}, nil
}
// Look for the primary group in the list of group IDs
for i, v := range g {
if v == u.Gid {
// Remove the element from the slice
g = append(g[:i], g[i+1:]...)
}
}
return User{
UID: u.Uid,
Name: u.Name,
PrimaryGID: u.Gid,
SupplementalGID: g,
}, nil
}
func getCurrentUserGroups() ([]string, error) {
var nilArray []string
out, _, err := command.Run("id", "--groups")
if err != nil {
return nilArray, err
}
out = strings.TrimSpace(out)
if out == "" {
return nilArray, fmt.Errorf("Unable to determine groups for current user")
}
groups := strings.Split(out, " ")
return groups, nil
}

View File

@@ -12,17 +12,17 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
image: ibmcom/mq:9 image: ibmcom/mq:9.1.1.0
manifests: manifests:
- image: ibmcom/mq:9.1.0.0-x86_64 - image: ibmcom/mq:9.1.1.0-x86_64
platform: platform:
architecture: amd64 architecture: amd64
os: linux os: linux
- image: ibmcom/mq:9.1.0.0-ppc64le - image: ibmcom/mq:9.1.1.0-ppc64le
platform: platform:
architecture: ppc64le architecture: ppc64le
os: linux os: linux
- image: ibmcom/mq:9.1.0.0-s390x - image: ibmcom/mq:9.1.1.0-s390x
platform: platform:
architecture: s390x architecture: s390x
os: linux os: linux

View File

@@ -0,0 +1,28 @@
# © Copyright IBM Corporation 2019
#
# 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.
image: ibmcom/mq:9.1.2.0-UBI
manifests:
- image: ibmcom/mq:9.1.2.0-UBI-amd64
platform:
architecture: amd64
os: linux
- image: ibmcom/mq:9.1.2.0-UBI-ppc64le
platform:
architecture: ppc64le
os: linux
- image: ibmcom/mq:9.1.2.0-UBI-s390x
platform:
architecture: s390x
os: linux

View File

@@ -0,0 +1,29 @@
# © Copyright IBM Corporation 2018, 2019
#
# 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.
image: ibmcom/mq:9.1.2.0
manifests:
- image: ibmcom/mq:9.1.2.0-x86_64
platform:
architecture: amd64
os: linux
- image: ibmcom/mq:9.1.2.0-ppc64le
platform:
architecture: ppc64le
os: linux
- image: ibmcom/mq:9.1.2.0-s390x
platform:
architecture: s390x
os: linux

View File

@@ -1,4 +1,4 @@
# © Copyright IBM Corporation 2018 # © Copyright IBM Corporation 2018, 2019
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -14,16 +14,15 @@
image: ibmcom/mq:latest image: ibmcom/mq:latest
manifests: manifests:
- image: ibmcom/mq:9.1.0.0-x86_64 - image: ibmcom/mq:9.1.2.0-UBI-amd64
platform: platform:
architecture: amd64 architecture: amd64
os: linux os: linux
- image: ibmcom/mq:9.1.0.0-ppc64le - image: ibmcom/mq:9.1.2.0-UBI-ppc64le
platform: platform:
architecture: ppc64le architecture: ppc64le
os: linux os: linux
- image: ibmcom/mq:9.1.0.0-s390x - image: ibmcom/mq:9.1.2.0-UBI-s390x
platform: platform:
architecture: s390x architecture: s390x
os: linux os: linux

View File

@@ -12,22 +12,18 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
ARG BASE_IMAGE=mq-sdk:9.0.5.0-x86_64-ubuntu-16.04 image: ibmcorp/mqadvanced-server-dev:9.1.1.0
manifests:
- image: ibmcorp/mqadvanced-server-dev:9.1.1.0-x86_64
platform:
architecture: amd64
os: linux
- image: ibmcorp/mqadvanced-server-dev:9.1.1.0-ppc64le
platform:
architecture: ppc64le
os: linux
- image: ibmcorp/mqadvanced-server-dev:9.1.1.0-s390x
platform:
architecture: s390x
os: linux
FROM $BASE_IMAGE
COPY incubating/mq-golang-sdk/install-golang.sh /usr/local/bin
ENV GO_VERSION=1.10
ENV PATH="${PATH}:/usr/lib/go-${GO_VERSION}/bin:/go/bin:/usr/local/go/bin" \
CGO_CFLAGS="-I/opt/mqm/inc/" \
CGO_LDFLAGS_ALLOW="-Wl,-rpath.*" \
GOPATH="/go"
# Install the Go compiler and Git
RUN chmod +x /usr/local/bin/install-golang.sh \
&& sleep 1 \
&& install-golang.sh
WORKDIR $GOPATH

View File

@@ -0,0 +1,28 @@
# © Copyright IBM Corporation 2019
#
# 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.
image: ibmcorp/mqadvanced-server-dev:9.1.2.0-UBI
manifests:
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-UBI-amd64
platform:
architecture: amd64
os: linux
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-UBI-ppc64le
platform:
architecture: ppc64le
os: linux
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-UBI-s390x
platform:
architecture: s390x
os: linux

View File

@@ -0,0 +1,29 @@
# © Copyright IBM Corporation 2018, 2019
#
# 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.
image: ibmcorp/mqadvanced-server-dev:9.1.2.0
manifests:
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-x86_64
platform:
architecture: amd64
os: linux
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-ppc64le
platform:
architecture: ppc64le
os: linux
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-s390x
platform:
architecture: s390x
os: linux

View File

@@ -0,0 +1,5 @@
# RHEL-based container build
Build scripts for building a container image based on Red Hat Enterprise Linux (RHEL), using the [`buildah`](https://github.com/containers/buildah) tool. buildah is supported on RHEL V7.5 and greater.
WARNING: The code in this directory is not currently in use, pending deletion. The MQ container is now built using a Red Hat UBI image, using "podman build".

View File

@@ -0,0 +1,49 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2018, 2019
#
#
# 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.
# Builds and tests the golang programs used by the MQ image.
set -ex
# Handle a GOPATH with multiple entries (just choose the first one)
IFS=':' read -ra DIR <<< "$GOPATH"
cd ${DIR[0]}/src/github.com/ibm-messaging/mq-container/
# Build and test the Go code
mkdir -p build
cd build
rm -f chkmqready chkmqhealthy runmqserver runmqdevserver
if [ "$MQDEV" = "TRUE" ]; then
# Build and test the Go code
go build -ldflags "-X \"main.ImageCreated=$(date --iso-8601=seconds)\" -X \"main.ImageRevision=$IMAGE_REVISION\" -X \"main.ImageSource=$IMAGE_SOURCE\"" --tags 'mqdev' ../cmd/runmqserver/
go build ../cmd/runmqdevserver/
else
go build -ldflags "-X \"main.ImageCreated=$(date --iso-8601=seconds)\" -X \"main.ImageRevision=$IMAGE_REVISION\" -X \"main.ImageSource=$IMAGE_SOURCE\"" ../cmd/runmqserver/
fi
go build ../cmd/chkmqready/
go build ../cmd/chkmqhealthy/
go test -v ../cmd/runmqserver/
go test -v ../cmd/chkmqready/
go test -v ../cmd/chkmqhealthy/
if [ "$MQDEV" = "TRUE" ]; then
go test -v ../cmd/runmqdevserver
fi
go test -v ../internal/...
go vet ../cmd/... ../internal/...

View File

@@ -0,0 +1,49 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2018, 2019
#
#
# 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.
# Run the Go build script inside the Go container, mounting the source
# directory in
function usage {
echo "Usage: $0 TAG DevModeFlag"
exit 20
}
if [ "$#" -ne 2 ]; then
echo "ERROR: Invalid number of parameters"
usage
fi
readonly tag=$1
readonly dev=$2
IMAGE_REVISION=${IMAGE_REVISION:="Not Applicable"}
IMAGE_SOURCE=${IMAGE_SOURCE:="Not Applicable"}
# Run the build in a container
# Note the ":Z" on the volume is to allow the container to access the files when SELinux is enabled
# Note the "podman" network is used explicitly, to avoid problems other CNI networks (e.g. on an OpenShift node)
podman run \
--volume ${PWD}:/opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/:Z \
--env IMAGE_REVISION="$IMAGE_REVISION" \
--env IMAGE_SOURCE="$IMAGE_SOURCE" \
--env MQDEV=${dev} \
--user $(id -u) \
--rm \
--network podman \
${tag} \
bash -c "cd /opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/ && ./mq-advanced-server-rhel/go-build.sh"

View File

@@ -0,0 +1,88 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2018, 2019
#
#
# 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.
# Install one or more MQ components into a buildah container
set -ex
function usage {
echo "Usage: $0 MQContainer MountLocation ARCHIVENAME PACKAGES"
exit 20
}
if [ "$#" -ne 4 ]; then
echo "ERROR: Invalid number of parameters"
usage
fi
readonly ctr_mq=$1
readonly mnt_mq=$2
readonly archive=$3
readonly mq_packages=$4
readonly dir_extract=/tmp/extract
readonly mqm_uid=888
readonly mqm_gid=888
if [ ! -d ${dir_extract}/MQServer ]; then
mkdir -p ${dir_extract}
echo Extracting $archive
tar -zxf $archive -C ${dir_extract}
echo Extracting finished
fi
# Accept the MQ license
buildah run --user root --volume ${dir_extract}:/mnt/mq-download:Z $ctr_mq -- /mnt/mq-download/MQServer/mqlicense.sh -text_only -accept
# Install MQ
buildah run --user root --volume ${dir_extract}:/mnt/mq-download:Z $ctr_mq -- bash -c "cd /mnt/mq-download/MQServer && rpm -ivh $mq_packages"
rm -rf ${dir_extract}/MQServer
# Remove 32-bit libraries from 64-bit container
find $mnt_mq/opt/mqm $mnt_mq/var/mqm -type f -exec file {} \; | awk -F: '/ELF 32-bit/{print $1}' | xargs --no-run-if-empty rm -f
# Remove tar.gz files unpacked by RPM postinst scripts
find $mnt_mq/opt/mqm -name '*.tar.gz' -delete
# Recommended: Set the default MQ installation (makes the MQ commands available on the PATH)
buildah run $ctr_mq -- /opt/mqm/bin/setmqinst -p /opt/mqm -i
mkdir -p $mnt_mq/run/runmqserver
chown ${mqm_uid}:${mqm_gid} $mnt_mq/run/runmqserver
# Remove the directory structure under /var/mqm which was created by the installer
rm -rf $mnt_mq/var/mqm
# Create the mount point for volumes, ensuring MQ has permissions to all directories
mkdir -p $mnt_mq/mnt/mqm
install --directory --mode 0775 --owner ${mqm_uid} --group root $mnt_mq/mnt
install --directory --mode 0775 --owner ${mqm_uid} --group root $mnt_mq/mnt/mqm
install --directory --mode 0775 --owner ${mqm_uid} --group root $mnt_mq/mnt/mqm/data
# Create the directory for MQ configuration files
mkdir -p /etc/mqm
install --directory --mode 0775 --owner ${mqm_uid} --group root $mnt_mq/etc/mqm
# Create a symlink for /var/mqm -> /mnt/mqm/data
buildah run --user root $ctr_mq -- ln -s /mnt/mqm/data /var/mqm
# Optional: Set these values for the IBM Cloud Vulnerability Report
sed -i 's/PASS_MAX_DAYS\t99999/PASS_MAX_DAYS\t90/' $mnt_mq/etc/login.defs
sed -i 's/PASS_MIN_DAYS\t0/PASS_MIN_DAYS\t1/' $mnt_mq/etc/login.defs
sed -i 's/password\t\[success=1 default=ignore\]\tpam_unix\.so obscure sha512/password\t[success=1 default=ignore]\tpam_unix.so obscure sha512 minlen=8/' $mnt_mq/etc/pam.d/password-auth
buildah run $ctr_mq -- cp -rs /opt/mqm/licenses/ /

View File

@@ -0,0 +1,173 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2018, 2019
#
#
# 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.
# Build a RHEL image, using the buildah tool
set -x
set -e
function usage {
echo "Usage: $0 ARCHIVE-NAME PACKAGES TAG VERSION MQDevFlag"
exit 20
}
if [ "$#" -ne 5 ]; then
echo "ERROR: Invalid number of parameters"
usage
fi
###############################################################################
# Setup MQ server working container
###############################################################################
# Use RHEL 7 minimal container (which doesn't include things like Python or Yum)
readonly ctr_mq=$(buildah from rhel7-minimal)
if [ -z "$ctr_mq" ]
then
echo "ERROR: ctr_mq is empty. Check above output for errors"
exit 50
fi
readonly mnt_mq=$(buildah mount $ctr_mq)
if [ -z "$mnt_mq" ]
then
echo "ERROR: mnt_mq is empty. Check above output for errors"
exit 50
fi
readonly archive=downloads/$1
readonly packages=$2
readonly tag=$3
readonly version=$4
readonly mqdev=$5
readonly mqm_uid=888
readonly mqm_gid=888
###############################################################################
# Install MQ server
###############################################################################
microdnf_opts="--nodocs"
# Check whether the host is registered with Red Hat
if subscription-manager status ; then
# Host is subscribed, but the minimal image has no enabled repos
# Note that the "bc" package is the only one in "extras"
microdnf_opts="${microdnf_opts} --enablerepo=rhel-7-server-rpms --enablerepo=rhel-7-server-extras-rpms"
else
# Use the Yum repositories configured on the host
cp -R /etc/yum.repos.d/* ${mnt_mq}/etc/yum.repos.d/
fi
buildah run ${ctr_mq} -- microdnf ${microdnf_opts} install \
bash \
bc \
coreutils \
file \
findutils \
gawk \
glibc-common \
grep \
passwd \
procps-ng \
sed \
shadow-utils \
tar \
util-linux \
which
# Install "sudo" if using MQ Advanced for Developers
if [ "$mqdev" = "TRUE" ]; then
buildah run ${ctr_mq} -- microdnf ${microdnf_opts} install sudo
fi
# Clean up cached files
buildah run ${ctr_mq} -- microdnf ${microdnf_opts} clean all
rm -rf ${mnt_mq}/etc/yum.repos.d/*
buildah run --user root $ctr_mq -- groupadd --system --gid ${mqm_gid} mqm
buildah run --user root $ctr_mq -- useradd --system --uid ${mqm_uid} --gid mqm --groups 0 mqm
# Install MQ server packages into the MQ builder image
./mq-advanced-server-rhel/install-mq-rhel.sh ${ctr_mq} "${mnt_mq}" "${archive}" "${packages}"
# Create the directory for MQ configuration files
mkdir -p ${mnt_mq}/etc/mqm
chown ${mqm_uid}:${mqm_gid} ${mnt_mq}/etc/mqm
# Install the Go binaries into the image
install --mode 0750 --owner ${mqm_uid} --group 0 ./build/runmqserver ${mnt_mq}/usr/local/bin/
install --mode 6750 --owner ${mqm_uid} --group 0 ./build/chk* ${mnt_mq}/usr/local/bin/
install --mode 0750 --owner ${mqm_uid} --group 0 ./NOTICES.txt ${mnt_mq}/opt/mqm/licenses/notices-container.txt
install --directory --mode 0775 --owner ${mqm_uid} --group 0 ${mnt_mq}/run/runmqserver
buildah run --user root $ctr_mq -- touch /run/termination-log
buildah run --user root $ctr_mq -- chown mqm:root /run/termination-log
buildah run --user root $ctr_mq -- chmod 0660 /run/termination-log
# Copy in licenses from installed packages
install --mode 0550 --owner root --group root ./mq-advanced-server-rhel/writePackages.sh ${mnt_mq}/usr/local/bin/writePackages
buildah run --user root $ctr_mq -- /usr/local/bin/writePackages
# Copy web XML files
cp -R web ${mnt_mq}/etc/mqm/web
# Make "mqm" the owner of all the config files
chown --recursive ${mqm_uid}:${mqm_gid} ${mnt_mq}/etc/mqm/*
chmod --recursive 0750 ${mnt_mq}/etc/mqm/*
###############################################################################
# Final Buildah commands
###############################################################################
if [ "$mqdev" = "TRUE" ]; then
OSTAG="mq messaging developer"
DISNAME="IBM MQ Advanced Server Developer Edition"
PID="98102d16795c4263ad9ca075190a2d4d"
else
OSTAG="mq messaging"
DISNAME="IBM MQ Advanced Server"
PID="4486e8c4cc9146fd9b3ce1f14a2dfc5b"
fi
buildah config \
--port 1414/tcp \
--port 9157/tcp \
--port 9443/tcp \
--os linux \
--label architecture=amd64 \
--label io.openshift.tags="$OSTAG" \
--label io.k8s.display-name="$DISNAME" \
--label io.k8s.description="IBM MQ is messaging middleware that simplifies and accelerates the integration of diverse applications and business data across multiple platforms. It uses message queues to facilitate the exchanges of information and offers a single messaging solution for cloud, mobile, Internet of Things (IoT) and on-premises environments." \
--label name="${tag%:*}" \
--label vendor="IBM" \
--label version="$version" \
--label release="1" \
--label run="docker run -d -e LICENSE=accept --name ibm-mq ${tag%:*}" \
--label summary="$DISNAME" \
--label description="IBM MQ is messaging middleware that simplifies and accelerates the integration of diverse applications and business data across multiple platforms. It uses message queues to facilitate the exchanges of information and offers a single messaging solution for cloud, mobile, Internet of Things (IoT) and on-premises environments." \
--label IBM_PRODUCT_ID="$PID" \
--label IBM_PRODUCT_NAME="$DISNAME" \
--label IBM_PRODUCT_VERSION="$version" \
--env AMQ_ADDITIONAL_JSON_LOG=1 \
--env LANG=en_US.UTF-8 \
--env LOG_FORMAT=basic \
--entrypoint runmqserver \
--user ${mqm_uid} \
$ctr_mq
buildah unmount $ctr_mq
buildah commit $ctr_mq $tag
buildah rm $ctr_mq

View File

@@ -0,0 +1,65 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2018, 2019
#
#
# 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.
# Build a RHEL image for building Go programs which use MQ
set -ex
function usage {
echo "Usage: $0 REDIST-ARCHIVE-NAME TAG"
exit 20
}
if [ "$#" -ne 2 ]; then
echo "ERROR: Invalid number of parameters"
usage
fi
readonly mq_redist_archive=downloads/$1
readonly tag=$2
# Use Red Hat's Go toolset image as the base
readonly ctr_mq=$(buildah from devtools/go-toolset-7-rhel7)
if [ -z "$ctr_mq" ]
then
echo "ERROR: ctr_mq is empty. Check above output for errors"
exit 50
fi
readonly mnt_mq_go=$(buildah mount $ctr_mq)
if [ -z "$mnt_mq_go" ]
then
echo "ERROR: mnt_mq_go is empty. Check above output for errors"
exit 50
fi
# Install the MQ redistributable client (including header files) into the Go builder image
mkdir -p ${mnt_mq_go}/opt/mqm
tar -xzf ${mq_redist_archive} -C ${mnt_mq_go}/opt/mqm
# Clean up Yum files
rm -rf ${mnt_mq_go}/etc/yum.repos.d/*
buildah unmount ${ctr_mq}
# Set environment variables for MQ/Go compilation
buildah config \
--os linux \
--env CGO_CFLAGS="-I/opt/mqm/inc/" \
--env CGO_LDFLAGS_ALLOW="-Wl,-rpath.*" \
${ctr_mq}
buildah commit ${ctr_mq} ${tag}
buildah rm ${ctr_mq}

View File

@@ -0,0 +1,121 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2018, 2019
#
#
# 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.
# Build a RHEL image of MQ Advanced for Developers, using the buildah tool
set -x
set -e
function usage {
echo "Usage: $0 BASETAG TAG VERSION"
exit 20
}
if [ "$#" -ne 3 ]; then
echo "ERROR: Invalid number of parameters"
usage
fi
###############################################################################
# Setup MQ server working container
###############################################################################
# Use a "scratch" container, so the resulting image has minimal files
# Resulting image won't have yum, for example
readonly basetag=$1
readonly ctr_mq=$(buildah from $basetag)
if [ -z "$ctr_mq" ]
then
echo "ERROR: ctr_mq is empty. Check above output for errors"
exit 50
fi
readonly mnt_mq=$(buildah mount $ctr_mq)
if [ -z "$mnt_mq" ]
then
echo "ERROR: mnt_mq is empty. Check above output for errors"
exit 50
fi
readonly tag=$2
readonly version=$3
readonly mqm_uid=888
readonly mqm_gid=888
# WARNING: This is what allows the mqm user to change the password of any other user
# It's used by runmqdevserver to change the admin/app passwords.
echo "mqm ALL = NOPASSWD: /usr/sbin/chpasswd" > $mnt_mq/etc/sudoers.d/mq-dev-config
# Run these commands inside the container so that the SELinux context is handled correctly
buildah run --user root $ctr_mq -- useradd --gid mqm admin
buildah run --user root $ctr_mq -- groupadd --system mqclient
buildah run --user root $ctr_mq -- useradd --gid mqclient app
buildah run --user root $ctr_mq -- bash -c "echo admin:passw0rd | chpasswd"
mkdir --parents $mnt_mq/run/runmqdevserver
chown ${mqm_uid}:${mqm_gid} $mnt_mq/run/runmqdevserver
# Copy runmqdevserver program
install --mode 0750 --owner ${mqm_uid} --group ${mqm_gid} ./build/runmqdevserver ${mnt_mq}/usr/local/bin/
install --directory --mode 0775 --owner ${mqm_uid} --group 0 ${mnt_mq}/run/runmqdevserver
# Copy template files
cp ./incubating/mqadvanced-server-dev/*.tpl ${mnt_mq}/etc/mqm/
# Copy web XML files for default developer configuration
cp -R incubating/mqadvanced-server-dev/web/ ${mnt_mq}/etc/mqm/web
# Make "mqm" the owner of all the config files
chown --recursive ${mqm_uid}:${mqm_gid} ${mnt_mq}/etc/mqm/*
chmod --recursive 0750 ${mnt_mq}/etc/mqm/*
###############################################################################
# Final Buildah commands
###############################################################################
buildah config \
--port 1414/tcp \
--port 9157/tcp \
--port 9443/tcp \
--os linux \
--label architecture=amd64 \
--label io.openshift.tags="mq messaging developer" \
--label io.k8s.display-name="IBM MQ Advanced Server Developer Edition" \
--label io.k8s.description="IBM MQ is messaging middleware that simplifies and accelerates the integration of diverse applications and business data across multiple platforms. It uses message queues to facilitate the exchanges of information and offers a single messaging solution for cloud, mobile, Internet of Things (IoT) and on-premises environments." \
--label name="${tag%:*}" \
--label vendor="IBM" \
--label version="$version" \
--label release="1" \
--label run="docker run -d -e LICENSE=accept --name ibm-mq-dev ${tag%:*}" \
--label summary="IBM MQ Advanced Server Developer Edition" \
--label description="IBM MQ is messaging middleware that simplifies and accelerates the integration of diverse applications and business data across multiple platforms. It uses message queues to facilitate the exchanges of information and offers a single messaging solution for cloud, mobile, Internet of Things (IoT) and on-premises environments." \
--label IBM_PRODUCT_ID="98102d16795c4263ad9ca075190a2d4d" \
--label IBM_PRODUCT_NAME="IBM MQ Advanced Server Developer Edition" \
--label IBM_PRODUCT_VERSION="$version" \
--env AMQ_ADDITIONAL_JSON_LOG=1 \
--env LANG=en_US.UTF-8 \
--env LOG_FORMAT=basic \
--env MQ_ADMIN_PASSWORD=passw0rd \
--env MQ_DEV=true \
--entrypoint runmqdevserver \
--user ${mqm_uid} \
$ctr_mq
buildah unmount $ctr_mq
buildah commit $ctr_mq $tag
buildah rm $ctr_mq

View File

@@ -0,0 +1,30 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2018, 2019
#
#
# 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.
# Copy in licenses from installed packages
set -e
rm -f /licenses/installed_package_notices
for p in $(rpm -qa | sort)
do
rpm -qi $p >> /licenses/installed_package_notices
printf "\n" >> /licenses/installed_package_notices
done
chmod 0444 /licenses/installed_package_notices

View File

@@ -18,7 +18,7 @@
[[constraint]] [[constraint]]
name = "github.com/docker/go-connections" name = "github.com/docker/go-connections"
version = "0.3.0" version = "0.4.0"
[prune] [prune]
go-tests = true go-tests = true

View File

@@ -1,7 +1,7 @@
// +build mqdev // +build mqdev
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -33,14 +33,16 @@ import (
// Note: This test requires a separate container image to be available for the JMS tests. // Note: This test requires a separate container image to be available for the JMS tests.
func TestDevGoldenPath(t *testing.T) { func TestDevGoldenPath(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
qm := "qm1"
containerConfig := container.Config{ containerConfig := container.Config{
Env: []string{ Env: []string{
"LICENSE=accept", "LICENSE=accept",
"MQ_QMGR_NAME=qm1", "MQ_QMGR_NAME=" + qm,
}, },
} }
id := runContainerWithPorts(t, cli, &containerConfig, []int{9443}) id := runContainerWithPorts(t, cli, &containerConfig, []int{9443})
@@ -49,7 +51,13 @@ func TestDevGoldenPath(t *testing.T) {
waitForWebReady(t, cli, id, insecureTLSConfig) waitForWebReady(t, cli, id, insecureTLSConfig)
t.Run("JMS", func(t *testing.T) { t.Run("JMS", func(t *testing.T) {
// Run the JMS tests, with no password specified // Run the JMS tests, with no password specified
runJMSTests(t, cli, id, false, "app", "") runJMSTests(t, cli, id, false, "app", defaultAppPasswordOS)
})
t.Run("REST admin", func(t *testing.T) {
testRESTAdmin(t, cli, id, insecureTLSConfig)
})
t.Run("REST messaging", func(t *testing.T) {
testRESTMessaging(t, cli, id, insecureTLSConfig, qm, "app", defaultAppPasswordWeb)
}) })
// Stop the container cleanly // Stop the container cleanly
stopContainer(t, cli, id) stopContainer(t, cli, id)
@@ -59,16 +67,20 @@ func TestDevGoldenPath(t *testing.T) {
// Note: This test requires a separate container image to be available for the JMS tests // Note: This test requires a separate container image to be available for the JMS tests
func TestDevSecure(t *testing.T) { func TestDevSecure(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
const tlsPassPhrase string = "passw0rd" const tlsPassPhrase string = "passw0rd"
qm := "qm1"
appPassword := "differentPassw0rd"
containerConfig := container.Config{ containerConfig := container.Config{
Env: []string{ Env: []string{
"LICENSE=accept", "LICENSE=accept",
"MQ_QMGR_NAME=qm1", "MQ_QMGR_NAME=" + qm,
"MQ_APP_PASSWORD=" + devAppPassword, "MQ_APP_PASSWORD=" + appPassword,
"MQ_TLS_KEYSTORE=/var/tls/server.p12", "MQ_TLS_KEYSTORE=/var/tls/server.p12",
"MQ_TLS_PASSPHRASE=" + tlsPassPhrase, "MQ_TLS_PASSPHRASE=" + tlsPassPhrase,
"DEBUG=1", "DEBUG=1",
@@ -100,13 +112,24 @@ func TestDevSecure(t *testing.T) {
waitForReady(t, cli, ctr.ID) waitForReady(t, cli, ctr.ID)
cert := filepath.Join(tlsDir(t, true), "server.crt") cert := filepath.Join(tlsDir(t, true), "server.crt")
waitForWebReady(t, cli, ctr.ID, createTLSConfig(t, cert, tlsPassPhrase)) waitForWebReady(t, cli, ctr.ID, createTLSConfig(t, cert, tlsPassPhrase))
runJMSTests(t, cli, ctr.ID, true, "app", devAppPassword)
t.Run("JMS", func(t *testing.T) {
runJMSTests(t, cli, ctr.ID, true, "app", appPassword)
})
t.Run("REST admin", func(t *testing.T) {
testRESTAdmin(t, cli, ctr.ID, insecureTLSConfig)
})
t.Run("REST messaging", func(t *testing.T) {
testRESTMessaging(t, cli, ctr.ID, insecureTLSConfig, qm, "app", appPassword)
})
// Stop the container cleanly // Stop the container cleanly
stopContainer(t, cli, ctr.ID) stopContainer(t, cli, ctr.ID)
} }
func TestDevWebDisabled(t *testing.T) { func TestDevWebDisabled(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -115,7 +138,7 @@ func TestDevWebDisabled(t *testing.T) {
Env: []string{ Env: []string{
"LICENSE=accept", "LICENSE=accept",
"MQ_QMGR_NAME=qm1", "MQ_QMGR_NAME=qm1",
"MQ_DISABLE_WEB_CONSOLE=true", "MQ_ENABLE_EMBEDDED_WEB_SERVER=false",
}, },
} }
id := runContainer(t, cli, &containerConfig) id := runContainer(t, cli, &containerConfig)
@@ -129,7 +152,7 @@ func TestDevWebDisabled(t *testing.T) {
}) })
t.Run("JMS", func(t *testing.T) { t.Run("JMS", func(t *testing.T) {
// Run the JMS tests, with no password specified // Run the JMS tests, with no password specified
runJMSTests(t, cli, id, false, "app", "") runJMSTests(t, cli, id, false, "app", defaultAppPasswordOS)
}) })
// Stop the container cleanly // Stop the container cleanly
stopContainer(t, cli, id) stopContainer(t, cli, id)
@@ -137,6 +160,7 @@ func TestDevWebDisabled(t *testing.T) {
func TestDevConfigDisabled(t *testing.T) { func TestDevConfigDisabled(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@@ -1,7 +1,7 @@
// +build mqdev // +build mqdev
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -18,12 +18,15 @@ limitations under the License.
package main package main
import ( import (
"bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httputil"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
@@ -34,8 +37,9 @@ import (
"github.com/docker/docker/client" "github.com/docker/docker/client"
) )
const devAdminPassword string = "passw0rd" const defaultAdminPassword string = "passw0rd"
const devAppPassword string = "passw0rd" const defaultAppPasswordOS string = ""
const defaultAppPasswordWeb string = "passw0rd"
// Disable TLS verification (server uses a self-signed certificate by default, // Disable TLS verification (server uses a self-signed certificate by default,
// so verification isn't useful anyway) // so verification isn't useful anyway)
@@ -58,7 +62,7 @@ func waitForWebReady(t *testing.T, cli *client.Client, ID string, tlsConfig *tls
select { select {
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
req.SetBasicAuth("admin", devAdminPassword) req.SetBasicAuth("admin", defaultAdminPassword)
resp, err := httpClient.Do(req.WithContext(ctx)) resp, err := httpClient.Do(req.WithContext(ctx))
if err == nil && resp.StatusCode == http.StatusOK { if err == nil && resp.StatusCode == http.StatusOK {
t.Log("MQ web server is ready") t.Log("MQ web server is ready")
@@ -83,6 +87,7 @@ func runJMSTests(t *testing.T, cli *client.Client, ID string, tls bool, user, pa
"MQ_PORT_1414_TCP_ADDR=" + getIPAddress(t, cli, ID), "MQ_PORT_1414_TCP_ADDR=" + getIPAddress(t, cli, ID),
"MQ_USERNAME=" + user, "MQ_USERNAME=" + user,
"MQ_CHANNEL=DEV.APP.SVRCONN", "MQ_CHANNEL=DEV.APP.SVRCONN",
"IBMJRE=" + os.Getenv("IBMJRE"),
}, },
Image: imageNameDevJMS(), Image: imageNameDevJMS(),
} }
@@ -109,7 +114,7 @@ func runJMSTests(t *testing.T, cli *client.Client, ID string, tls bool, user, pa
t.Fatal(err) t.Fatal(err)
} }
startContainer(t, cli, ctr.ID) startContainer(t, cli, ctr.ID)
rc := waitForContainer(t, cli, ctr.ID, 10) rc := waitForContainer(t, cli, ctr.ID, 2*time.Minute)
if rc != 0 { if rc != 0 {
t.Errorf("JUnit container failed with rc=%v", rc) t.Errorf("JUnit container failed with rc=%v", rc)
} }
@@ -140,17 +145,16 @@ func createTLSConfig(t *testing.T, certFile, password string) *tls.Config {
} }
} }
func testREST(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config) { func testRESTAdmin(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config) {
httpClient := http.Client{ httpClient := http.Client{
Timeout: time.Duration(30 * time.Second), Timeout: time.Duration(30 * time.Second),
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
}, },
} }
url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", getPort(t, cli, ID, 9443)) url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", getPort(t, cli, ID, 9443))
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
req.SetBasicAuth("admin", devAdminPassword) req.SetBasicAuth("admin", defaultAdminPassword)
resp, err := httpClient.Do(req) resp, err := httpClient.Do(req)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -159,3 +163,70 @@ func testREST(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config
t.Errorf("Expected HTTP status code %v from 'GET installation'; got %v", http.StatusOK, resp.StatusCode) t.Errorf("Expected HTTP status code %v from 'GET installation'; got %v", http.StatusOK, resp.StatusCode)
} }
} }
// curl -i -k https://localhost:1234/ibmmq/rest/v1/messaging/qmgr/qm1/queue/DEV.QUEUE.1/message -X POST -u app -H “ibm-mq-rest-csrf-token: N/A” -H “Content-Type: text/plain;charset=utf-8" -d “Hello World”
func logHTTPRequest(t *testing.T, req *http.Request) {
d, err := httputil.DumpRequestOut(req, true)
if err != nil {
t.Error(err)
}
t.Logf("HTTP request: %v", string(d))
}
func logHTTPResponse(t *testing.T, resp *http.Response) {
d, err := httputil.DumpResponse(resp, true)
if err != nil {
t.Error(err)
}
t.Logf("HTTP response: %v", string(d))
}
func testRESTMessaging(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config, qmName string, user string, password string) {
httpClient := http.Client{
Timeout: time.Duration(30 * time.Second),
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
}
q := "DEV.QUEUE.1"
url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/messaging/qmgr/%s/queue/%s/message", getPort(t, cli, ID, 9443), qmName, q)
putMessage := []byte("Hello")
req, err := http.NewRequest("POST", url, bytes.NewBuffer(putMessage))
req.SetBasicAuth(user, password)
req.Header.Add("ibm-mq-rest-csrf-token", "n/a")
req.Header.Add("Content-Type", "text/plain;charset=utf-8")
logHTTPRequest(t, req)
resp, err := httpClient.Do(req)
if err != nil {
t.Fatal(err)
}
logHTTPResponse(t, resp)
if resp.StatusCode != http.StatusCreated {
t.Errorf("Expected HTTP status code %v from 'POST to queue'; got %v", http.StatusOK, resp.StatusCode)
t.Logf("HTTP response: %+v", resp)
t.Fail()
}
req, err = http.NewRequest("DELETE", url, nil)
req.Header.Add("ibm-mq-rest-csrf-token", "n/a")
req.SetBasicAuth(user, password)
logHTTPRequest(t, req)
resp, err = httpClient.Do(req)
if err != nil {
t.Fatal(err)
}
logHTTPResponse(t, resp)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected HTTP status code %v from 'DELETE from queue'; got %v", http.StatusOK, resp.StatusCode)
t.Logf("HTTP response: %+v", resp)
t.Fail()
}
gotMessage, err := ioutil.ReadAll(resp.Body)
//gotMessage := string(b)
if string(gotMessage) != string(putMessage) {
t.Errorf("Expected payload to be \"%s\"; got \"%s\"", putMessage, gotMessage)
}
}

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2017, 2018 © Copyright IBM Corporation 2017, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -24,7 +24,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@@ -38,6 +37,7 @@ import (
func TestLicenseNotSet(t *testing.T) { func TestLicenseNotSet(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -45,15 +45,16 @@ func TestLicenseNotSet(t *testing.T) {
containerConfig := container.Config{} containerConfig := container.Config{}
id := runContainer(t, cli, &containerConfig) id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id) defer cleanContainer(t, cli, id)
rc := waitForContainer(t, cli, id, 5) rc := waitForContainer(t, cli, id, 20*time.Second)
if rc != 1 { if rc != 1 {
t.Errorf("Expected rc=1, got rc=%v", rc) t.Errorf("Expected rc=1, got rc=%v", rc)
} }
expectTerminationMessage(t) expectTerminationMessage(t, cli, id)
} }
func TestLicenseView(t *testing.T) { func TestLicenseView(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -63,7 +64,7 @@ func TestLicenseView(t *testing.T) {
} }
id := runContainer(t, cli, &containerConfig) id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id) defer cleanContainer(t, cli, id)
rc := waitForContainer(t, cli, id, 5) rc := waitForContainer(t, cli, id, 20*time.Second)
if rc != 1 { if rc != 1 {
t.Errorf("Expected rc=1, got rc=%v", rc) t.Errorf("Expected rc=1, got rc=%v", rc)
} }
@@ -77,12 +78,14 @@ func TestLicenseView(t *testing.T) {
// TestGoldenPath starts a queue manager successfully when metrics are enabled // TestGoldenPath starts a queue manager successfully when metrics are enabled
func TestGoldenPathWithMetrics(t *testing.T) { func TestGoldenPathWithMetrics(t *testing.T) {
t.Parallel() t.Parallel()
goldenPath(t, true) goldenPath(t, true)
} }
// TestGoldenPath starts a queue manager successfully when metrics are disabled // TestGoldenPath starts a queue manager successfully when metrics are disabled
func TestGoldenPathNoMetrics(t *testing.T) { func TestGoldenPathNoMetrics(t *testing.T) {
t.Parallel() t.Parallel()
goldenPath(t, false) goldenPath(t, false)
} }
@@ -107,32 +110,40 @@ func goldenPath(t *testing.T, metric bool) {
} }
// TestSecurityVulnerabilities checks for any vulnerabilities in the image, as reported // TestSecurityVulnerabilities checks for any vulnerabilities in the image, as reported
// by Ubuntu // by Red Hat
func TestSecurityVulnerabilities(t *testing.T) { func TestSecurityVulnerabilities(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
rc, _ := runContainerOneShot(t, cli, "bash", "-c", "test -d /etc/apt") rc, _ := runContainerOneShot(t, cli, "bash", "-c", "command -v microdnf && test -e /etc/yum.repos.d/ubi.repo")
if rc != 0 { if rc != 0 {
t.Skip("Skipping test because container is not Ubuntu-based") t.Skip("Skipping test because container is based on ubi-minimal, which doesn't include yum")
}
// Override the entrypoint to make "apt" only receive security updates, then check for updates
var url string
if runtime.GOARCH == "amd64" {
url = "http://security.ubuntu.com/ubuntu/"
} else {
url = "http://ports.ubuntu.com/ubuntu-ports/"
}
rc, log := runContainerOneShot(t, cli, "bash", "-c", "source /etc/os-release && echo \"deb "+url+" ${VERSION_CODENAME}-security main restricted\" > /etc/apt/sources.list && apt-get update 2>&1 >/dev/null && apt-get --simulate -qq upgrade")
if rc != 0 {
t.Fatalf("Expected success, got %v", rc)
}
lines := strings.Split(strings.TrimSpace(log), "\n")
if len(lines) > 0 && lines[0] != "" {
t.Errorf("Expected no vulnerabilities, found the following:\n%v", log)
} }
// id, _, err := command.Run("sudo", "buildah", "from", imageName())
// if err != nil {
// t.Log(id)
// t.Fatal(err)
// }
// id = strings.TrimSpace(id)
// defer command.Run("buildah", "rm", id)
// mnt, _, err := command.Run("sudo", "buildah", "mount", id)
// if err != nil {
// t.Log(mnt)
// t.Fatal(err)
// }
// mnt = strings.TrimSpace(mnt)
// out, _, err := command.Run("bash", "-c", "sudo cp /etc/yum.repos.d/* "+filepath.Join(mnt, "/etc/yum.repos.d/"))
// if err != nil {
// t.Log(out)
// t.Fatal(err)
// }
// out, ret, _ := command.Run("bash", "-c", "yum --installroot="+mnt+" updateinfo list sec | grep /Sec")
// if ret != 1 {
// t.Errorf("Expected no vulnerabilities, found the following:\n%v", out)
// }
} }
func utilTestNoQueueManagerName(t *testing.T, hostName string, expectedName string) { func utilTestNoQueueManagerName(t *testing.T, hostName string, expectedName string) {
@@ -155,11 +166,13 @@ func utilTestNoQueueManagerName(t *testing.T, hostName string, expectedName stri
} }
func TestNoQueueManagerName(t *testing.T) { func TestNoQueueManagerName(t *testing.T) {
t.Parallel() t.Parallel()
utilTestNoQueueManagerName(t, "test", "test") utilTestNoQueueManagerName(t, "test", "test")
} }
func TestNoQueueManagerNameInvalidHostname(t *testing.T) { func TestNoQueueManagerNameInvalidHostname(t *testing.T) {
t.Parallel() t.Parallel()
utilTestNoQueueManagerName(t, "test-1", "test1") utilTestNoQueueManagerName(t, "test-1", "test1")
} }
@@ -167,6 +180,7 @@ func TestNoQueueManagerNameInvalidHostname(t *testing.T) {
// container and starts a new one with same volume. With metrics enabled // container and starts a new one with same volume. With metrics enabled
func TestWithVolumeAndMetrics(t *testing.T) { func TestWithVolumeAndMetrics(t *testing.T) {
t.Parallel() t.Parallel()
withVolume(t, true) withVolume(t, true)
} }
@@ -174,6 +188,7 @@ func TestWithVolumeAndMetrics(t *testing.T) {
// container and starts a new one with same volume. With metrics disabled // container and starts a new one with same volume. With metrics disabled
func TestWithVolumeNoMetrics(t *testing.T) { func TestWithVolumeNoMetrics(t *testing.T) {
t.Parallel() t.Parallel()
withVolume(t, false) withVolume(t, false)
} }
@@ -183,7 +198,7 @@ func withVolume(t *testing.T, metric bool) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
vol := createVolume(t, cli) vol := createVolume(t, cli, t.Name())
defer removeVolume(t, cli, vol.Name) defer removeVolume(t, cli, vol.Name)
containerConfig := container.Config{ containerConfig := container.Config{
Image: imageName(), Image: imageName(),
@@ -221,10 +236,67 @@ func withVolume(t *testing.T, metric bool) {
waitForReady(t, cli, ctr2.ID) waitForReady(t, cli, ctr2.ID)
} }
// TestWithSplitVolumesLogsData starts a queue manager with separate log/data mounts
func TestWithSplitVolumesLogsData(t *testing.T) {
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
defer removeVolume(t, cli, qmsharedlogs.Name)
qmshareddata := createVolume(t, cli, "qmshareddata")
defer removeVolume(t, cli, qmshareddata.Name)
err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, qmshareddata.Name, []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"})
defer removeVolume(t, cli, qmVol)
defer cleanContainer(t, cli, qmID)
waitForReady(t, cli, qmID)
}
// TestWithSplitVolumesLogsOnly starts a queue manager with a separate log mount
func TestWithSplitVolumesLogsOnly(t *testing.T) {
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
defer removeVolume(t, cli, qmsharedlogs.Name)
err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, "", []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"})
defer removeVolume(t, cli, qmVol)
defer cleanContainer(t, cli, qmID)
waitForReady(t, cli, qmID)
}
// TestWithSplitVolumesDataOnly starts a queue manager with a separate data mount
func TestWithSplitVolumesDataOnly(t *testing.T) {
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
qmshareddata := createVolume(t, cli, "qmshareddata")
defer removeVolume(t, cli, qmshareddata.Name)
err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, "", qmshareddata.Name, []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"})
defer removeVolume(t, cli, qmVol)
defer cleanContainer(t, cli, qmID)
waitForReady(t, cli, qmID)
}
// TestNoVolumeWithRestart ensures a queue manager container can be stopped // TestNoVolumeWithRestart ensures a queue manager container can be stopped
// and restarted cleanly // and restarted cleanly
func TestNoVolumeWithRestart(t *testing.T) { func TestNoVolumeWithRestart(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -240,58 +312,138 @@ func TestNoVolumeWithRestart(t *testing.T) {
waitForReady(t, cli, id) waitForReady(t, cli, id)
} }
// TestCreateQueueManagerFail causes a failure of `crtmqm` // TestVolumeRequiresRoot tests the case where only the root user can write
func TestCreateQueueManagerFail(t *testing.T) { // to the persistent volume. In this case, an "init container" is needed,
t.Parallel() // where `runmqserver -i` is run to initialize the storage. Then the
// container can be run as normal.
func TestVolumeRequiresRoot(t *testing.T) {
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
img, _, err := cli.ImageInspectWithRaw(context.Background(), imageName()) vol := createVolume(t, cli, t.Name())
defer removeVolume(t, cli, vol.Name)
// Set permissions on the volume to only allow root to write it
// It's important that read and execute permissions are given to other users
rc, _ := runContainerOneShotWithVolume(t, cli, vol.Name+":/mnt/mqm:nocopy", "bash", "-c", "chown 65534:4294967294 /mnt/mqm/ && chmod 0755 /mnt/mqm/ && ls -lan /mnt/mqm/")
if rc != 0 {
t.Errorf("Expected one shot container to return rc=0, got rc=%v", rc)
}
containerConfig := container.Config{
Image: imageName(),
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
}
hostConfig := container.HostConfig{
Binds: []string{
coverageBind(t),
vol.Name + ":/mnt/mqm:nocopy",
},
}
networkingConfig := network.NetworkingConfig{}
// Run an "init container" as root, with the "-i" option, to initialize the volume
containerConfig = container.Config{
Image: imageName(),
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=true"},
User: "0",
Entrypoint: []string{"runmqserver", "-i"},
}
initCtr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"Init")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
oldEntrypoint := strings.Join(img.Config.Entrypoint, " ") defer cleanContainer(t, cli, initCtr.ID)
t.Logf("Init container ID=%v", initCtr.ID)
startContainer(t, cli, initCtr.ID)
rc = waitForContainer(t, cli, initCtr.ID, 20*time.Second)
if rc != 0 {
t.Errorf("Expected init container to exit with rc=0, got rc=%v", rc)
}
containerConfig = container.Config{
Image: imageName(),
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=true"},
}
ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"Main")
if err != nil {
t.Fatal(err)
}
defer cleanContainer(t, cli, ctr.ID)
t.Logf("Main container ID=%v", ctr.ID)
startContainer(t, cli, ctr.ID)
waitForReady(t, cli, ctr.ID)
}
// TestCreateQueueManagerFail causes a failure of `crtmqm`
func TestCreateQueueManagerFail(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
var files = []struct {
Name, Body string
}{
{"Dockerfile", fmt.Sprintf(`
FROM %v
USER root
RUN echo '#!/bin/bash\nexit 999' > /opt/mqm/bin/crtmqm
RUN chown mqm:mqm /opt/mqm/bin/crtmqm
RUN chmod 6550 /opt/mqm/bin/crtmqm
USER mqm`, imageName())},
}
tag := createImage(t, cli, files)
defer deleteImage(t, cli, tag)
containerConfig := container.Config{ containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"}, Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
// Override the entrypoint to create the queue manager directory, but leave it empty. Image: tag,
// This will cause `crtmqm` to return with an exit code of 2.
Entrypoint: []string{"bash", "-c", "mkdir -p /mnt/mqm/data && mkdir -p /var/mqm/qmgrs/qm1 && exec " + oldEntrypoint},
} }
id := runContainer(t, cli, &containerConfig) id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id) defer cleanContainer(t, cli, id)
rc := waitForContainer(t, cli, id, 10) rc := waitForContainer(t, cli, id, 10*time.Second)
if rc != 1 { if rc != 1 {
t.Errorf("Expected rc=1, got rc=%v", rc) t.Errorf("Expected rc=1, got rc=%v", rc)
} }
expectTerminationMessage(t) expectTerminationMessage(t, cli, id)
} }
// TestStartQueueManagerFail causes a failure of `strmqm` // TestStartQueueManagerFail causes a failure of `strmqm`
func TestStartQueueManagerFail(t *testing.T) { func TestStartQueueManagerFail(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
img, _, err := cli.ImageInspectWithRaw(context.Background(), imageName()) var files = []struct {
if err != nil { Name, Body string
t.Fatal(err) }{
{"Dockerfile", fmt.Sprintf(`
FROM %v
USER root
RUN echo '#!/bin/bash\ndltmqm $@ && strmqm $@' > /opt/mqm/bin/strmqm
RUN chown mqm:mqm /opt/mqm/bin/strmqm
RUN chmod 6550 /opt/mqm/bin/strmqm
USER mqm`, imageName())},
} }
oldEntrypoint := strings.Join(img.Config.Entrypoint, " ") tag := createImage(t, cli, files)
defer deleteImage(t, cli, tag)
containerConfig := container.Config{ containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=1"}, Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
// Override the entrypoint to replace `strmqm` with a script which deletes the queue manager. Image: tag,
// This will cause `strmqm` to return with an exit code of 72.
Entrypoint: []string{"bash", "-c", "echo '#!/bin/bash\ndltmqm $@ && strmqm $@' > /opt/mqm/bin/strmqm && exec " + oldEntrypoint},
} }
id := runContainer(t, cli, &containerConfig) id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id) defer cleanContainer(t, cli, id)
rc := waitForContainer(t, cli, id, 10) rc := waitForContainer(t, cli, id, 20*time.Second)
if rc != 1 { if rc != 1 {
t.Errorf("Expected rc=1, got rc=%v", rc) t.Errorf("Expected rc=1, got rc=%v", rc)
} }
expectTerminationMessage(t) expectTerminationMessage(t, cli, id)
} }
// TestVolumeUnmount runs a queue manager with a volume, and then forces an // TestVolumeUnmount runs a queue manager with a volume, and then forces an
@@ -300,11 +452,12 @@ func TestStartQueueManagerFail(t *testing.T) {
// attached storage gets unmounted. // attached storage gets unmounted.
func TestVolumeUnmount(t *testing.T) { func TestVolumeUnmount(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
vol := createVolume(t, cli) vol := createVolume(t, cli, t.Name())
defer removeVolume(t, cli, vol.Name) defer removeVolume(t, cli, vol.Name)
containerConfig := container.Config{ containerConfig := container.Config{
Image: imageName(), Image: imageName(),
@@ -348,6 +501,7 @@ func TestVolumeUnmount(t *testing.T) {
// created, then checks that no zombies exist (runmqserver should reap them) // created, then checks that no zombies exist (runmqserver should reap them)
func TestZombies(t *testing.T) { func TestZombies(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -384,6 +538,7 @@ func TestZombies(t *testing.T) {
// on that image, and checks that the MQSC has been applied correctly. // on that image, and checks that the MQSC has been applied correctly.
func TestMQSC(t *testing.T) { func TestMQSC(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -391,7 +546,13 @@ func TestMQSC(t *testing.T) {
var files = []struct { var files = []struct {
Name, Body string Name, Body string
}{ }{
{"Dockerfile", fmt.Sprintf("FROM %v\nRUN rm -f /etc/mqm/*.mqsc\nADD test.mqsc /etc/mqm/", imageName())}, {"Dockerfile", fmt.Sprintf(`
FROM %v
USER root
RUN rm -f /etc/mqm/*.mqsc
ADD test.mqsc /etc/mqm/
RUN chmod 0660 /etc/mqm/test.mqsc
USER mqm`, imageName())},
{"test.mqsc", "DEFINE QLOCAL(test)"}, {"test.mqsc", "DEFINE QLOCAL(test)"},
} }
tag := createImage(t, cli, files) tag := createImage(t, cli, files)
@@ -411,11 +572,244 @@ func TestMQSC(t *testing.T) {
} }
} }
// TestLargeMQSC creates a new image with a large MQSC file in, starts a container based
// on that image, and checks that the MQSC has been applied correctly.
func TestLargeMQSC(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
const numQueues = 1000
var buf bytes.Buffer
for i := 1; i <= numQueues; i++ {
fmt.Fprintf(&buf, "* Test processing of a large MQSC file, defining queue test%v\nDEFINE QLOCAL(test%v)\n", i, i)
}
var files = []struct {
Name, Body string
}{
{"Dockerfile", fmt.Sprintf(`
FROM %v
USER root
RUN rm -f /etc/mqm/*.mqsc
ADD test.mqsc /etc/mqm/
RUN chmod 0660 /etc/mqm/test.mqsc
USER mqm`, imageName())},
{"test.mqsc", buf.String()},
}
tag := createImage(t, cli, files)
defer deleteImage(t, cli, tag)
containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
Image: tag,
}
id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id)
waitForReady(t, cli, id)
rc, mqscOutput := execContainer(t, cli, id, "mqm", []string{"bash", "-c", "echo 'DISPLAY QLOCAL(test" + strconv.Itoa(numQueues) + ")' | runmqsc"})
if rc != 0 {
r := regexp.MustCompile("AMQ[0-9][0-9][0-9][0-9]E")
t.Fatalf("Expected runmqsc to exit with rc=0, got %v with error %v", rc, r.FindString(mqscOutput))
}
}
// TestRedactValidMQSC creates a new image with a Valid MQSC file that contains sensitive information, starts a container based
// on that image, and checks that the MQSC has been redacted in the logs.
func TestRedactValidMQSC(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
passwords := "hippoman4567"
sslcryp := fmt.Sprintf("GSK_PKCS11=/usr/lib/pkcs11/PKCS11_API.so;token-label;%s;SYMMETRIC_CIPHER_ON;", passwords)
/* LDAPPWD*/
fmt.Fprintf(&buf, "DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) CONNAME('test(24)') SHORTUSR('sn') LDAPUSER('user') LDAPPWD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) ldappwd('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) lDaPpWd('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD \t('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) +\n LDAP+\n PWD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) -\nLDAPP-\nWD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) +\n*test comment\n LDAPP-\n*test comment2\nWD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(%v)\n", passwords)
/* PASSWORD */
fmt.Fprintf(&buf, "DEFINE CHANNEL(TEST2) CHLTYPE(SDR) CONNAME('test(24)') XMITQ('fake') PASSWORD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) CHLTYPE(SDR) password('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) CHLTYPE(SDR) pAsSwOrD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) CHLTYPE(SDR) PASSWORD \t('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) +\n CHLTYPE(SDR) PASS+\n WORD+\n ('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) -\nCHLTYPE(SDR) PASS-\nWORD-\n('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) +\n CHLTYPE(SDR) PASS-\n*comemnt 2\nWORD+\n*test comment\n ('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) CHLTYPE(SDR) PASSWORD(%s)\n", passwords)
/* SSLCRYP */
fmt.Fprintf(&buf, "ALTER QMGR SSLCRYP('%v')\n", sslcryp)
fmt.Fprintf(&buf, "ALTER QMGR sslcryp('%v')\n", sslcryp)
fmt.Fprintf(&buf, "ALTER QMGR SsLcRyP('%v')\n", sslcryp)
fmt.Fprintf(&buf, "ALTER QMGR SSLCRYP \t('%v')\n", sslcryp)
fmt.Fprintf(&buf, "ALTER QMGR +\n SSL+\n CRYP+\n ('%v')\n", sslcryp)
fmt.Fprintf(&buf, "ALTER QMGR -\nSSLC-\nRYP-\n('%v')\n", sslcryp)
fmt.Fprintf(&buf, "ALTER QMGR +\n*commenttime\n SSL-\n*commentagain\nCRYP+\n*last comment\n ('%v')\n", sslcryp)
var files = []struct {
Name, Body string
}{
{"Dockerfile", fmt.Sprintf(`
FROM %v
USER root
RUN rm -f /etc/mqm/*.mqsc
ADD test.mqsc /etc/mqm/
RUN chmod 0660 /etc/mqm/test.mqsc
USER mqm`, imageName())},
{"test.mqsc", buf.String()},
}
tag := createImage(t, cli, files)
defer deleteImage(t, cli, tag)
containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
Image: tag,
}
id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id)
waitForReady(t, cli, id)
stopContainer(t, cli, id)
scanner := bufio.NewScanner(strings.NewReader(inspectLogs(t, cli, id)))
for scanner.Scan() {
s := scanner.Text()
if strings.Contains(s, sslcryp) || strings.Contains(s, passwords) {
t.Fatalf("Expected redacted MQSC output, got: %v", s)
}
}
err = scanner.Err()
if err != nil {
t.Fatal(err)
}
}
// TestRedactValidMQSC creates a new image with a Invalid MQSC file that contains sensitive information, starts a container based
// on that image, and checks that the MQSC has been redacted in the logs.
func TestRedactInvalidMQSC(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
passwords := "hippoman4567"
sslcryp := fmt.Sprintf("GSK_PKCS11=/usr/lib/pkcs11/PKCS11_API.so;token-label;%s;SYMMETRIC_CIPHER_ON;", passwords)
/* LDAPPWD*/
fmt.Fprintf(&buf, "DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) CONNAME('test(24)') SHORTUSR('sn') LDAPUSER('user') LDAPPWD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPPPPPP('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD['%v']\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(ARGHHH) LDAPPWD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) ARGHAHA(IDPWLDAP) LDAPPWD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD '%v'\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('%v') badvalues\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) badvales LDAPPWD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD{'%v'}\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD<'%v'>\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('%v'+\n p['il6])\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('%v'/653***)\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD('%v'\n DISPLAY QMGR", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD('%v💩')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD💩('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAP+\n 💩PWD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) 💩 LDAPPWD('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD 💩 ('%v')\n", passwords)
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD('%v') 💩\n", passwords)
fmt.Fprintf(&buf, "ALTER 💩 AUTHINFO(TEST) LDAPPWD('%v')\n", passwords)
var files = []struct {
Name, Body string
}{
{"Dockerfile", fmt.Sprintf(`
FROM %v
USER root
RUN rm -f /etc/mqm/*.mqsc
ADD test.mqsc /etc/mqm/
RUN chmod 0660 /etc/mqm/test.mqsc
USER mqm`, imageName())},
{"test.mqsc", buf.String()},
}
tag := createImage(t, cli, files)
defer deleteImage(t, cli, tag)
containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
Image: tag,
}
id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id)
rc := waitForContainer(t, cli, id, 20*time.Second)
if rc != 1 {
t.Errorf("Expected rc=1, got rc=%v", rc)
}
scanner := bufio.NewScanner(strings.NewReader(inspectLogs(t, cli, id)))
for scanner.Scan() {
s := scanner.Text()
if strings.Contains(s, sslcryp) || strings.Contains(s, passwords) {
t.Fatalf("Expected redacted MQSC output, got: %v", s)
}
}
err = scanner.Err()
if err != nil {
t.Fatal(err)
}
}
// TestInvalidMQSC creates a new image with an MQSC file containing invalid MQSC,
// tries to start a container based on that image, and checks that container terminates
func TestInvalidMQSC(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
var files = []struct {
Name, Body string
}{
{"Dockerfile", fmt.Sprintf(`
FROM %v
USER root
RUN rm -f /etc/mqm/*.mqsc
ADD mqscTest.mqsc /etc/mqm/
RUN chmod 0660 /etc/mqm/mqscTest.mqsc
USER mqm`, imageName())},
{"mqscTest.mqsc", "DEFINE INVALIDLISTENER('TEST.LISTENER.TCP') TRPTYPE(TCP) PORT(1414) CONTROL(QMGR) REPLACE"},
}
tag := createImage(t, cli, files)
defer deleteImage(t, cli, tag)
containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
Image: tag,
}
id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id)
rc := waitForContainer(t, cli, id, 60*time.Second)
if rc != 1 {
t.Errorf("Expected rc=1, got rc=%v", rc)
}
expectTerminationMessage(t, cli, id)
}
// TestReadiness creates a new image with large amounts of MQSC in, to // TestReadiness creates a new image with large amounts of MQSC in, to
// ensure that the readiness check doesn't pass until configuration has finished. // ensure that the readiness check doesn't pass until configuration has finished.
// WARNING: This test is sensitive to the speed of the machine it's running on. // WARNING: This test is sensitive to the speed of the machine it's running on.
func TestReadiness(t *testing.T) { func TestReadiness(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -428,7 +822,13 @@ func TestReadiness(t *testing.T) {
var files = []struct { var files = []struct {
Name, Body string Name, Body string
}{ }{
{"Dockerfile", fmt.Sprintf("FROM %v\nRUN rm -f /etc/mqm/*.mqsc\nADD test.mqsc /etc/mqm/", imageName())}, {"Dockerfile", fmt.Sprintf(`
FROM %v
USER root
RUN rm -f /etc/mqm/*.mqsc
ADD test.mqsc /etc/mqm/
RUN chmod 0660 /etc/mqm/test.mqsc
USER mqm`, imageName())},
{"test.mqsc", buf.String()}, {"test.mqsc", buf.String()},
} }
tag := createImage(t, cli, files) tag := createImage(t, cli, files)
@@ -464,22 +864,34 @@ func TestReadiness(t *testing.T) {
func TestErrorLogRotation(t *testing.T) { func TestErrorLogRotation(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
logsize := 65536
rc, _ := runContainerOneShot(t, cli, "bash", "-c", "test -d /etc/apt")
if rc != 0 {
// RHEL
logsize = 32768
}
qmName := "qm1" qmName := "qm1"
containerConfig := container.Config{ containerConfig := container.Config{
Env: []string{ Env: []string{
"LICENSE=accept", "LICENSE=accept",
"MQ_QMGR_NAME=" + qmName, "MQ_QMGR_NAME=" + qmName,
"MQMAXERRORLOGSIZE=65536", fmt.Sprintf("MQMAXERRORLOGSIZE=%d", logsize),
"LOG_FORMAT=json", "LOG_FORMAT=json",
fmt.Sprintf("AMQ_EXTRA_QM_STANZAS=QMErrorLog:ErrorLogSize=%d", logsize),
}, },
ExposedPorts: nat.PortSet{ ExposedPorts: nat.PortSet{
"1414/tcp": struct{}{}, "1414/tcp": struct{}{},
}, },
} }
id := runContainer(t, cli, &containerConfig) id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id) defer cleanContainer(t, cli, id)
waitForReady(t, cli, id) waitForReady(t, cli, id)
@@ -487,6 +899,7 @@ func TestErrorLogRotation(t *testing.T) {
// Generate some content for the error logs, by trying to put messages under an unauthorized user // Generate some content for the error logs, by trying to put messages under an unauthorized user
// execContainer(t, cli, id, "fred", []string{"bash", "-c", "for i in {1..30} ; do /opt/mqm/samp/bin/amqsput FAKE; done"}) // execContainer(t, cli, id, "fred", []string{"bash", "-c", "for i in {1..30} ; do /opt/mqm/samp/bin/amqsput FAKE; done"})
execContainer(t, cli, id, "root", []string{"useradd", "fred"}) execContainer(t, cli, id, "root", []string{"useradd", "fred"})
for { for {
execContainer(t, cli, id, "fred", []string{"bash", "-c", "/opt/mqm/samp/bin/amqsput FAKE"}) execContainer(t, cli, id, "fred", []string{"bash", "-c", "/opt/mqm/samp/bin/amqsput FAKE"})
@@ -529,12 +942,14 @@ func TestErrorLogRotation(t *testing.T) {
// Tests the log comes out in JSON format when JSON format is enabled. With metrics enabled // Tests the log comes out in JSON format when JSON format is enabled. With metrics enabled
func TestJSONLogFormatWithMetrics(t *testing.T) { func TestJSONLogFormatWithMetrics(t *testing.T) {
t.Parallel() t.Parallel()
jsonLogFormat(t, true) jsonLogFormat(t, true)
} }
// Tests the log comes out in JSON format when JSON format is enabled. With metrics disabled // Tests the log comes out in JSON format when JSON format is enabled. With metrics disabled
func TestJSONLogFormatNoMetrics(t *testing.T) { func TestJSONLogFormatNoMetrics(t *testing.T) {
t.Parallel() t.Parallel()
jsonLogFormat(t, false) jsonLogFormat(t, false)
} }
@@ -575,6 +990,7 @@ func jsonLogFormat(t *testing.T, metric bool) {
func TestBadLogFormat(t *testing.T) { func TestBadLogFormat(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -587,11 +1003,11 @@ func TestBadLogFormat(t *testing.T) {
} }
id := runContainer(t, cli, &containerConfig) id := runContainer(t, cli, &containerConfig)
defer cleanContainer(t, cli, id) defer cleanContainer(t, cli, id)
rc := waitForContainer(t, cli, id, 5) rc := waitForContainer(t, cli, id, 20*time.Second)
if rc != 1 { if rc != 1 {
t.Errorf("Expected rc=1, got rc=%v", rc) t.Errorf("Expected rc=1, got rc=%v", rc)
} }
expectTerminationMessage(t) expectTerminationMessage(t, cli, id)
} }
// TestMQJSONDisabled tests the case where MQ's JSON logging feature is // TestMQJSONDisabled tests the case where MQ's JSON logging feature is
@@ -684,10 +1100,11 @@ func TestVersioning(t *testing.T) {
dataAr := strings.Split(line, " ") dataAr := strings.Split(line, " ")
data := dataAr[len(dataAr)-1] data := dataAr[len(dataAr)-1]
// Verify created // Verify created is in a known timestamp format
_, err := time.Parse(time.RFC3339, data) _, err := time.Parse(time.RFC3339, data)
if err != nil { _, err2 := time.Parse("2006-01-02T15:04:05-0700", data)
t.Errorf("Failed to validate Image created (%v) - %v", data, err) if err != nil && err2 != nil {
t.Errorf("Failed to validate Image created stamp (%v) - %v or %v", data, time.RFC3339, "2006-01-02T15:04:05-0700")
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2017, 2018 © Copyright IBM Corporation 2017, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -43,6 +43,18 @@ import (
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
) )
type containerDetails struct {
ID string
Name string
Image string
Path string
Args []string
CapAdd []string
CapDrop []string
User string
Env []string
}
func imageName() string { func imageName() string {
image, ok := os.LookupEnv("TEST_IMAGE") image, ok := os.LookupEnv("TEST_IMAGE")
if !ok { if !ok {
@@ -59,6 +71,29 @@ func imageNameDevJMS() string {
return image return image
} }
// baseImage returns the ID of the underlying base image (e.g. "ubuntu" or "rhel")
func baseImage(t *testing.T, cli *client.Client) string {
rc, out := runContainerOneShot(t, cli, "grep", "^ID=", "/etc/os-release")
if rc != 0 {
t.Fatal("Couldn't determine base image")
}
s := strings.Split(out, "=")
if len(s) < 2 {
t.Fatal("Couldn't determine base image string")
}
return s[1]
}
// devImage returns true if the image under test is a developer image,
// determined by use of the MQ_ADMIN_PASSWORD environment variable
func devImage(t *testing.T, cli *client.Client) bool {
rc, _ := runContainerOneShot(t, cli, "printenv", "MQ_ADMIN_PASSWORD")
if rc == 0 {
return true
}
return false
}
// isWSL return whether we are running in the Windows Subsystem for Linux // isWSL return whether we are running in the Windows Subsystem for Linux
func isWSL(t *testing.T) bool { func isWSL(t *testing.T) bool {
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
@@ -124,66 +159,79 @@ func getTempDir(t *testing.T, unixStylePath bool) string {
return "/tmp/" return "/tmp/"
} }
// terminationLogUnixPath returns the name of the file to use for the termination log message, with a UNIX path
func terminationLogUnixPath(t *testing.T) string {
// Warning: this directory must be accessible to the Docker daemon,
// in order to enable the bind mount
return getTempDir(t, true) + t.Name() + "-termination-log"
}
// terminationLogOSPath returns the name of the file to use for the termination log message, with an OS specific path
func terminationLogOSPath(t *testing.T) string {
// Warning: this directory must be accessible to the Docker daemon,
// in order to enable the bind mount
return getTempDir(t, false) + t.Name() + "-termination-log"
}
// terminationBind returns a string to use to bind-mount a termination log file.
// This is done using a bind, because you can't copy files from /dev out of the container.
func terminationBind(t *testing.T) string {
n := terminationLogUnixPath(t)
// Remove it if it already exists
os.Remove(n)
// Create the empty file
f, err := os.OpenFile(n, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
t.Fatal(err)
}
f.Close()
return terminationLogOSPath(t) + ":/dev/termination-log"
}
// terminationMessage return the termination message, or an empty string if not set // terminationMessage return the termination message, or an empty string if not set
func terminationMessage(t *testing.T) string { func terminationMessage(t *testing.T, cli *client.Client, ID string) string {
b, err := ioutil.ReadFile(terminationLogUnixPath(t)) r, _, err := cli.CopyFromContainer(context.Background(), ID, "/run/termination-log")
if err != nil { if err != nil {
t.Log(err) t.Log(err)
return ""
} }
return string(b) b, err := ioutil.ReadAll(r)
tr := tar.NewReader(bytes.NewReader(b))
_, err = tr.Next()
if err != nil {
t.Log(err)
return ""
}
// read the complete content of the file h.Name into the bs []byte
content, err := ioutil.ReadAll(tr)
if err != nil {
t.Log(err)
return ""
}
return string(content)
} }
func expectTerminationMessage(t *testing.T) { func expectTerminationMessage(t *testing.T, cli *client.Client, ID string) {
m := terminationMessage(t) m := terminationMessage(t, cli, ID)
if m == "" { if m == "" {
t.Error("Expected termination message to be set") t.Error("Expected termination message to be set")
} }
} }
func cleanContainer(t *testing.T, cli *client.Client, ID string) { // logContainerDetails logs selected details about the container
func logContainerDetails(t *testing.T, cli *client.Client, ID string) {
i, err := cli.ContainerInspect(context.Background(), ID) i, err := cli.ContainerInspect(context.Background(), ID)
if err == nil { if err == nil {
// Log the results and continue d := containerDetails{
t.Logf("Inspected container %v: %#v", ID, i) ID: ID,
s, err := json.MarshalIndent(i, "", " ") Name: i.Name,
if err != nil { Image: i.Image,
t.Fatal(err) Path: i.Path,
Args: i.Args,
CapAdd: i.HostConfig.CapAdd,
CapDrop: i.HostConfig.CapDrop,
User: i.Config.User,
Env: i.Config.Env,
} }
t.Logf("Inspected container %v: %v", ID, string(s)) // If you need more details, you can always just run `json.MarshalIndent(i, "", " ")` to see everything.
t.Logf("Container details: %+v", d)
} }
}
func cleanContainerQuiet(t *testing.T, cli *client.Client, ID string) {
timeout := 10 * time.Second
err := cli.ContainerStop(context.Background(), ID, &timeout)
if err != nil {
// Just log the error and continue
t.Log(err)
}
opts := types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
}
err = cli.ContainerRemove(context.Background(), ID, opts)
if err != nil {
t.Error(err)
}
}
func cleanContainer(t *testing.T, cli *client.Client, ID string) {
logContainerDetails(t, cli, ID)
t.Logf("Stopping container: %v", ID) t.Logf("Stopping container: %v", ID)
timeout := 10 * time.Second timeout := 10 * time.Second
// Stop the container. This allows the coverage output to be generated. // Stop the container. This allows the coverage output to be generated.
err = cli.ContainerStop(context.Background(), ID, &timeout) err := cli.ContainerStop(context.Background(), ID, &timeout)
if err != nil { if err != nil {
// Just log the error and continue // Just log the error and continue
t.Log(err) t.Log(err)
@@ -195,11 +243,10 @@ func cleanContainer(t *testing.T, cli *client.Client, ID string) {
// Log the container output for any container we're about to delete // Log the container output for any container we're about to delete
t.Logf("Console log from container %v:\n%v", ID, inspectTextLogs(t, cli, ID)) t.Logf("Console log from container %v:\n%v", ID, inspectTextLogs(t, cli, ID))
m := terminationMessage(t) m := terminationMessage(t, cli, ID)
if m != "" { if m != "" {
t.Logf("Termination message: %v", m) t.Logf("Termination message: %v", m)
} }
os.Remove(terminationLogUnixPath(t))
t.Logf("Removing container: %s", ID) t.Logf("Removing container: %s", ID)
opts := types.ContainerRemoveOptions{ opts := types.ContainerRemoveOptions{
@@ -219,15 +266,36 @@ func runContainerWithPorts(t *testing.T, cli *client.Client, containerConfig *co
if containerConfig.Image == "" { if containerConfig.Image == "" {
containerConfig.Image = imageName() containerConfig.Image = imageName()
} }
// Always run as the "mqm" user, unless the test has specified otherwise
if containerConfig.User == "" {
containerConfig.User = "mqm"
}
// if coverage // if coverage
containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov") containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov")
containerConfig.Env = append(containerConfig.Env, "EXIT_CODE_FILE="+getExitCodeFilename(t)) containerConfig.Env = append(containerConfig.Env, "EXIT_CODE_FILE="+getExitCodeFilename(t))
hostConfig := container.HostConfig{ hostConfig := container.HostConfig{
Binds: []string{ Binds: []string{
coverageBind(t), coverageBind(t),
terminationBind(t),
}, },
PortBindings: nat.PortMap{}, PortBindings: nat.PortMap{},
CapDrop: []string{
"ALL",
},
}
if devImage(t, cli) {
t.Logf("Detected MQ Advanced for Developers image — adding extra Linux capabilities to container")
hostConfig.CapAdd = []string{
"CHOWN",
"SETUID",
"SETGID",
"AUDIT_WRITE",
}
// Only needed for a RHEL-based image
if baseImage(t, cli) != "ubuntu" {
hostConfig.CapAdd = append(hostConfig.CapAdd, "DAC_OVERRIDE")
}
} else {
t.Logf("Detected MQ Advanced image - dropping all capabilities")
} }
for _, p := range ports { for _, p := range ports {
port := nat.Port(fmt.Sprintf("%v/tcp", p)) port := nat.Port(fmt.Sprintf("%v/tcp", p))
@@ -254,13 +322,62 @@ func runContainer(t *testing.T, cli *client.Client, containerConfig *container.C
return runContainerWithPorts(t, cli, containerConfig, nil) return runContainerWithPorts(t, cli, containerConfig, nil)
} }
// runContainerOneShot runs a container with a custom entrypoint, as the root
// user and with default capabilities
func runContainerOneShot(t *testing.T, cli *client.Client, command ...string) (int64, string) { func runContainerOneShot(t *testing.T, cli *client.Client, command ...string) (int64, string) {
containerConfig := container.Config{ containerConfig := container.Config{
Entrypoint: command, Entrypoint: command,
User: "root",
Image: imageName(),
} }
id := runContainer(t, cli, &containerConfig) hostConfig := container.HostConfig{}
defer cleanContainer(t, cli, id) networkingConfig := network.NetworkingConfig{}
return waitForContainer(t, cli, id, 10), inspectLogs(t, cli, id) t.Logf("Running one shot container (%s): %v", containerConfig.Image, command)
ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"OneShot")
if err != nil {
t.Fatal(err)
}
startOptions := types.ContainerStartOptions{}
err = cli.ContainerStart(context.Background(), ctr.ID, startOptions)
if err != nil {
t.Fatal(err)
}
defer cleanContainerQuiet(t, cli, ctr.ID)
rc := waitForContainer(t, cli, ctr.ID, 20*time.Second)
out := inspectLogs(t, cli, ctr.ID)
t.Logf("One shot container finished with rc=%v, output=%v", rc, out)
return rc, out
}
// runContainerOneShot runs a container with a custom entrypoint, as the root
// user, with default capabilities, and a volume mounted
func runContainerOneShotWithVolume(t *testing.T, cli *client.Client, bind string, command ...string) (int64, string) {
containerConfig := container.Config{
Entrypoint: command,
User: "root",
Image: imageName(),
}
hostConfig := container.HostConfig{
Binds: []string{
bind,
},
}
networkingConfig := network.NetworkingConfig{}
t.Logf("Running one shot container with volume (%s): %v", containerConfig.Image, command)
ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"OneShotVolume")
if err != nil {
t.Fatal(err)
}
startOptions := types.ContainerStartOptions{}
err = cli.ContainerStart(context.Background(), ctr.ID, startOptions)
if err != nil {
t.Fatal(err)
}
defer cleanContainerQuiet(t, cli, ctr.ID)
rc := waitForContainer(t, cli, ctr.ID, 20*time.Second)
out := inspectLogs(t, cli, ctr.ID)
t.Logf("One shot container finished with rc=%v, output=%v", rc, out)
return rc, out
} }
func startContainer(t *testing.T, cli *client.Client, ID string) { func startContainer(t *testing.T, cli *client.Client, ID string) {
@@ -281,6 +398,14 @@ func stopContainer(t *testing.T, cli *client.Client, ID string) {
} }
} }
func killContainer(t *testing.T, cli *client.Client, ID string, signal string) {
t.Logf("Killing container: %v", ID)
err := cli.ContainerKill(context.Background(), ID, signal)
if err != nil {
t.Fatal(err)
}
}
func getExitCodeFilename(t *testing.T) string { func getExitCodeFilename(t *testing.T) string {
return t.Name() + "ExitCode" return t.Name() + "ExitCode"
} }
@@ -309,19 +434,19 @@ func getCoverageExitCode(t *testing.T, orig int64) int64 {
} }
// waitForContainer waits until a container has exited // waitForContainer waits until a container has exited
func waitForContainer(t *testing.T, cli *client.Client, ID string, timeout int64) int64 { func waitForContainer(t *testing.T, cli *client.Client, ID string, timeout time.Duration) int64 {
rc, err := cli.ContainerWait(context.Background(), ID) c, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
rc, err := cli.ContainerWait(c, ID)
if err != nil {
t.Fatal(err)
}
if coverage() { if coverage() {
// COVERAGE: When running coverage, the exit code is written to a file, // COVERAGE: When running coverage, the exit code is written to a file,
// to allow the coverage to be generated (which doesn't happen for non-zero // to allow the coverage to be generated (which doesn't happen for non-zero
// exit codes) // exit codes)
rc = getCoverageExitCode(t, rc) rc = getCoverageExitCode(t, rc)
} }
if err != nil {
t.Fatal(err)
}
return rc return rc
} }
@@ -395,7 +520,7 @@ func execContainer(t *testing.T, cli *client.Client, ID string, user string, cmd
} }
func waitForReady(t *testing.T, cli *client.Client, ID string) { func waitForReady(t *testing.T, cli *client.Client, ID string) {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel() defer cancel()
for { for {
@@ -405,6 +530,9 @@ func waitForReady(t *testing.T, cli *client.Client, ID string) {
if rc == 0 { if rc == 0 {
t.Log("MQ is ready") t.Log("MQ is ready")
return return
} else if rc == 10 {
t.Log("MQ Readiness: Queue Manager Running as Standby")
return
} }
case <-ctx.Done(): case <-ctx.Done():
t.Fatal("Timed out waiting for container to become ready") t.Fatal("Timed out waiting for container to become ready")
@@ -440,17 +568,17 @@ func removeNetwork(t *testing.T, cli *client.Client, ID string) {
} }
} }
func createVolume(t *testing.T, cli *client.Client) types.Volume { func createVolume(t *testing.T, cli *client.Client, name string) types.Volume {
v, err := cli.VolumeCreate(context.Background(), volume.VolumesCreateBody{ v, err := cli.VolumeCreate(context.Background(), volume.VolumesCreateBody{
Driver: "local", Driver: "local",
DriverOpts: map[string]string{}, DriverOpts: map[string]string{},
Labels: map[string]string{}, Labels: map[string]string{},
Name: t.Name(), Name: name,
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Logf("Created volume %v", t.Name()) t.Logf("Created volume %v", v.Name)
return v return v
} }

View File

@@ -0,0 +1,234 @@
/*
© Copyright IBM Corporation 2019
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 main
import (
"strings"
"testing"
"time"
"github.com/docker/docker/client"
)
var miEnv = []string{
"LICENSE=accept",
"MQ_QMGR_NAME=QM1",
"MQ_MULTI_INSTANCE=true",
}
// TestMultiInstanceStartStop creates 2 containers in a multi instance queue manager configuration
// and starts/stop them checking we always have an active and standby
func TestMultiInstanceStartStop(t *testing.T) {
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
err, qm1aId, qm1bId, volumes := configureMultiInstance(t, cli)
if err != nil {
t.Fatal(err)
}
for _, volume := range volumes {
defer removeVolume(t, cli, volume)
}
defer cleanContainer(t, cli, qm1aId)
defer cleanContainer(t, cli, qm1bId)
waitForReady(t, cli, qm1aId)
waitForReady(t, cli, qm1bId)
err, active, standby := getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
if err != nil {
t.Fatal(err)
}
killContainer(t, cli, active, "SIGTERM")
time.Sleep(2 * time.Second)
if status := getQueueManagerStatus(t, cli, standby, "QM1"); strings.Compare(status, "Running") != 0 {
t.Fatalf("Expected QM1 to be running as active queue manager, dspmq returned status of %v", status)
}
startContainer(t, cli, qm1aId)
waitForReady(t, cli, qm1aId)
err, _, _ = getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
if err != nil {
t.Fatal(err)
}
}
// TestMultiInstanceContainerStop starts 2 containers in a multi instance queue manager configuration,
// stops the active queue manager, then checks to ensure the backup queue manager becomes active
func TestMultiInstanceContainerStop(t *testing.T) {
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
err, qm1aId, qm1bId, volumes := configureMultiInstance(t, cli)
if err != nil {
t.Fatal(err)
}
for _, volume := range volumes {
defer removeVolume(t, cli, volume)
}
defer cleanContainer(t, cli, qm1aId)
defer cleanContainer(t, cli, qm1bId)
waitForReady(t, cli, qm1aId)
waitForReady(t, cli, qm1bId)
err, active, standby := getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
if err != nil {
t.Fatal(err)
}
stopContainer(t, cli, active)
if status := getQueueManagerStatus(t, cli, standby, "QM1"); strings.Compare(status, "Running") != 0 {
t.Fatalf("Expected QM1 to be running as active queue manager, dspmq returned status of %v", status)
}
}
// TestMultiInstanceRace starts 2 containers in separate goroutines in a multi instance queue manager
// configuration, then checks to ensure that both an active and standby queue manager have been started
func TestMultiInstanceRace(t *testing.T) {
t.Skipf("Skipping %v until file lock is implemented", t.Name())
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
defer removeVolume(t, cli, qmsharedlogs.Name)
qmshareddata := createVolume(t, cli, "qmshareddata")
defer removeVolume(t, cli, qmshareddata.Name)
qmsChannel := make(chan QMChan)
go singleMultiInstanceQueueManager(t, cli, qmsharedlogs.Name, qmshareddata.Name, qmsChannel)
go singleMultiInstanceQueueManager(t, cli, qmsharedlogs.Name, qmshareddata.Name, qmsChannel)
qm1a := <-qmsChannel
if qm1a.Error != nil {
t.Fatal(qm1a.Error)
}
qm1b := <-qmsChannel
if qm1b.Error != nil {
t.Fatal(qm1b.Error)
}
qm1aId, qm1aData := qm1a.QMId, qm1a.QMData
qm1bId, qm1bData := qm1b.QMId, qm1b.QMData
defer removeVolume(t, cli, qm1aData)
defer removeVolume(t, cli, qm1bData)
defer cleanContainer(t, cli, qm1aId)
defer cleanContainer(t, cli, qm1bId)
waitForReady(t, cli, qm1aId)
waitForReady(t, cli, qm1bId)
err, _, _ = getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
if err != nil {
t.Fatal(err)
}
}
// TestMultiInstanceNoSharedMounts starts 2 multi instance queue managers without providing shared log/data
// mounts, then checks to ensure that the container terminates with the expected message
func TestMultiInstanceNoSharedMounts(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, "", "", miEnv)
if err != nil {
t.Fatal(err)
}
defer removeVolume(t, cli, qm1aData)
defer cleanContainer(t, cli, qm1aId)
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm-log'", 30*time.Second)
}
// TestMultiInstanceNoSharedLogs starts 2 multi instance queue managers without providing a shared log
// mount, then checks to ensure that the container terminates with the expected message
func TestMultiInstanceNoSharedLogs(t *testing.T) {
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
qmshareddata := createVolume(t, cli, "qmshareddata")
defer removeVolume(t, cli, qmshareddata.Name)
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, "", qmshareddata.Name, miEnv)
if err != nil {
t.Fatal(err)
}
defer removeVolume(t, cli, qm1aData)
defer cleanContainer(t, cli, qm1aId)
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm-log'", 30*time.Second)
}
// TestMultiInstanceNoSharedData starts 2 multi instance queue managers without providing a shared data
// mount, then checks to ensure that the container terminates with the expected message
func TestMultiInstanceNoSharedData(t *testing.T) {
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
defer removeVolume(t, cli, qmsharedlogs.Name)
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, "", miEnv)
if err != nil {
t.Fatal(err)
}
defer removeVolume(t, cli, qm1aData)
defer cleanContainer(t, cli, qm1aId)
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm-data'", 30*time.Second)
}
// TestMultiInstanceNoMounts starts 2 multi instance queue managers without providing a shared data
// mount, then checks to ensure that the container terminates with the expected message
func TestMultiInstanceNoMounts(t *testing.T) {
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, false, "", "", miEnv)
if err != nil {
t.Fatal(err)
}
defer removeVolume(t, cli, qm1aData)
defer cleanContainer(t, cli, qm1aId)
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm'", 30*time.Second)
}

View File

@@ -0,0 +1,178 @@
/*
© Copyright IBM Corporation 2019
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 main
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
)
type QMChan struct {
QMId string
QMData string
Error error
}
// configureMultiInstance creates the volumes and containers required for basic testing
// of multi instance queue managers. Returns error, qm1a ID, qm1b ID, slice of volume names
func configureMultiInstance(t *testing.T, cli *client.Client) (error, string, string, []string) {
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
qmshareddata := createVolume(t, cli, "qmshareddata")
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, qmshareddata.Name, miEnv)
if err != nil {
return err, "", "", []string{}
}
time.Sleep(10 * time.Second)
err, qm1bId, qm1bData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, qmshareddata.Name, miEnv)
if err != nil {
return err, "", "", []string{}
}
volumes := []string{qmsharedlogs.Name, qmshareddata.Name, qm1aData, qm1bData}
return nil, qm1aId, qm1bId, volumes
}
func singleMultiInstanceQueueManager(t *testing.T, cli *client.Client, qmsharedlogs string, qmshareddata string, qmsChannel chan QMChan) {
err, qmId, qmData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, qmshareddata, miEnv)
if err != nil {
qmsChannel <- QMChan{Error: err}
}
qmsChannel <- QMChan{QMId: qmId, QMData: qmData}
}
func getHostConfig(t *testing.T, mounts int, qmsharedlogs string, qmshareddata string, qmdata string) container.HostConfig {
var hostConfig container.HostConfig
switch mounts {
case 1:
hostConfig = container.HostConfig{
Binds: []string{
coverageBind(t),
qmdata + ":/mnt/mqm",
},
}
case 2:
hostConfig = container.HostConfig{
Binds: []string{
coverageBind(t),
qmdata + ":/mnt/mqm",
qmshareddata + ":/mnt/mqm-data",
},
}
case 3:
hostConfig = container.HostConfig{
Binds: []string{
coverageBind(t),
qmdata + ":/mnt/mqm",
qmsharedlogs + ":/mnt/mqm-log",
},
}
case 4:
hostConfig = container.HostConfig{
Binds: []string{
coverageBind(t),
qmdata + ":/mnt/mqm",
qmsharedlogs + ":/mnt/mqm-log",
qmshareddata + ":/mnt/mqm-data",
},
}
}
return hostConfig
}
func startMultiVolumeQueueManager(t *testing.T, cli *client.Client, dataVol bool, qmsharedlogs string, qmshareddata string, env []string) (error, string, string) {
id := strconv.FormatInt(time.Now().UnixNano(), 10)
qmdata := createVolume(t, cli, id)
containerConfig := container.Config{
Image: imageName(),
Env: env,
}
var hostConfig container.HostConfig
if !dataVol {
hostConfig = container.HostConfig{}
} else if qmsharedlogs == "" && qmshareddata == "" {
hostConfig = getHostConfig(t, 1, "", "", qmdata.Name)
} else if qmsharedlogs == "" {
hostConfig = getHostConfig(t, 2, "", qmshareddata, qmdata.Name)
} else if qmshareddata == "" {
hostConfig = getHostConfig(t, 3, qmsharedlogs, "", qmdata.Name)
} else {
hostConfig = getHostConfig(t, 4, qmsharedlogs, qmshareddata, qmdata.Name)
}
networkingConfig := network.NetworkingConfig{}
qm, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+id)
if err != nil {
return err, "", ""
}
startContainer(t, cli, qm.ID)
return nil, qm.ID, qmdata.Name
}
func getActiveStandbyQueueManager(t *testing.T, cli *client.Client, qm1aId string, qm1bId string) (error, string, string) {
qm1aStatus := getQueueManagerStatus(t, cli, qm1aId, "QM1")
qm1bStatus := getQueueManagerStatus(t, cli, qm1bId, "QM1")
if qm1aStatus == "Running" && qm1bStatus == "Running as standby" {
return nil, qm1aId, qm1bId
} else if qm1bStatus == "Running" && qm1aStatus == "Running as standby" {
return nil, qm1bId, qm1aId
}
err := fmt.Errorf("Expected to be running in multi instance configuration, got status 1) %v status 2) %v", qm1aStatus, qm1bStatus)
return err, "", ""
}
func getQueueManagerStatus(t *testing.T, cli *client.Client, containerID string, queueManagerName string) string {
_, dspmqOut := execContainer(t, cli, containerID, "mqm", []string{"bash", "-c", "dspmq", "-m", queueManagerName})
regex := regexp.MustCompile(`STATUS\(.*\)`)
status := regex.FindString(dspmqOut)
status = strings.TrimSuffix(strings.TrimPrefix(status, "STATUS("), ")")
return status
}
func waitForTerminationMessage(t *testing.T, cli *client.Client, qmId string, terminationString string, timeout time.Duration) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
for {
select {
case <-time.After(1 * time.Second):
m := terminationMessage(t, cli, qmId)
if m != "" {
if !strings.Contains(m, terminationString) {
t.Fatalf("Expected container to fail on missing required mount. Got termination message: %v", m)
}
return
}
case <-ctx.Done():
t.Fatal("Timed out waiting for container to terminate")
}
}
}

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2018 © Copyright IBM Corporation 2018, 2019
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import (
func TestGoldenPathMetric(t *testing.T) { func TestGoldenPathMetric(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -53,6 +54,7 @@ func TestGoldenPathMetric(t *testing.T) {
func TestMetricNames(t *testing.T) { func TestMetricNames(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -96,6 +98,7 @@ func TestMetricNames(t *testing.T) {
func TestMetricLabels(t *testing.T) { func TestMetricLabels(t *testing.T) {
t.Parallel() t.Parallel()
requiredLabels := []string{"qmgr"} requiredLabels := []string{"qmgr"}
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
@@ -144,6 +147,7 @@ func TestMetricLabels(t *testing.T) {
func TestRapidFirePrometheus(t *testing.T) { func TestRapidFirePrometheus(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -177,6 +181,7 @@ func TestRapidFirePrometheus(t *testing.T) {
func TestSlowPrometheus(t *testing.T) { func TestSlowPrometheus(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -207,6 +212,7 @@ func TestSlowPrometheus(t *testing.T) {
func TestContainerRestart(t *testing.T) { func TestContainerRestart(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -254,6 +260,7 @@ func TestContainerRestart(t *testing.T) {
func TestQMRestart(t *testing.T) { func TestQMRestart(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -279,7 +286,7 @@ func TestQMRestart(t *testing.T) {
// Restart just the QM (to simulate a lost connection) // Restart just the QM (to simulate a lost connection)
t.Log("Stopping queue manager\n") t.Log("Stopping queue manager\n")
rc, out := execContainer(t, cli, id, "mqm", []string{"endmqm", "-w", defaultMetricQMName}) rc, out := execContainer(t, cli, id, "mqm", []string{"endmqm", "-w", "-r", defaultMetricQMName})
if rc != 0 { if rc != 0 {
t.Fatalf("Failed to stop the queue manager. rc=%d, err=%s", rc, out) t.Fatalf("Failed to stop the queue manager. rc=%d, err=%s", rc, out)
} }
@@ -311,6 +318,7 @@ func TestQMRestart(t *testing.T) {
func TestValidValues(t *testing.T) { func TestValidValues(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -346,6 +354,7 @@ func TestValidValues(t *testing.T) {
func TestChangingValues(t *testing.T) { func TestChangingValues(t *testing.T) {
t.Parallel() t.Parallel()
cli, err := client.NewEnvClient() cli, err := client.NewEnvClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@@ -1,4 +1,4 @@
# © Copyright IBM Corporation 2018 # © Copyright IBM Corporation 2018, 2019
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
############################################################################### ###############################################################################
# Application build environment (Maven) # Application build environment (Maven)
############################################################################### ###############################################################################
FROM maven:3-ibmjava as builder FROM docker.io/maven:3-ibmjava as builder
COPY pom.xml /usr/src/mymaven/ COPY pom.xml /usr/src/mymaven/
WORKDIR /usr/src/mymaven WORKDIR /usr/src/mymaven
# Download dependencies separately, so Docker caches them # Download dependencies separately, so Docker caches them
@@ -30,7 +30,7 @@ RUN find /usr/src/mymaven
############################################################################### ###############################################################################
# Application runtime (JRE only, no build environment) # Application runtime (JRE only, no build environment)
############################################################################### ###############################################################################
FROM ibmjava:sfj FROM docker.io/ibmjava:8-jre
COPY --from=builder /usr/src/mymaven/target/*.jar /opt/app/ COPY --from=builder /usr/src/mymaven/target/*.jar /opt/app/
COPY --from=builder /usr/src/mymaven/target/lib/*.jar /opt/app/ COPY --from=builder /usr/src/mymaven/target/lib/*.jar /opt/app/
ENTRYPOINT ["java", "-classpath", "/opt/app/*", "org.junit.platform.console.ConsoleLauncher", "-p", "com.ibm.mqcontainer.test", "--details", "verbose"] ENTRYPOINT ["java", "-classpath", "/opt/app/*", "org.junit.platform.console.ConsoleLauncher", "-p", "com.ibm.mqcontainer.test", "--details", "verbose"]

84
test/messaging/buildah.sh Executable file
View File

@@ -0,0 +1,84 @@
#!/bin/bash
# © Copyright IBM Corporation 2018, 2019
#
# 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.
set -x
set -e
###############################################################################
# Setup MQ JMS Test container
###############################################################################
# Use a "scratch" container, so the resulting image has minimal files
# Resulting image won't have yum, for example
readonly ctr_mq=$(buildah from rhel7-minimal)
readonly mnt_mq=$(buildah mount $ctr_mq)
readonly imagename=$1
microdnf_opts="--nodocs"
# Check whether the host is registered with Red Hat
if subscription-manager status ; then
# Host is subscribed, but the minimal image has no enabled repos
# Note that the "bc" package is the only one in "extras"
microdnf_opts="${microdnf_opts} --enablerepo=rhel-7-server-rpms --enablerepo=rhel-7-server-extras-rpms"
else
# Use the Yum repositories configured on the host
cp -R /etc/yum.repos.d/* ${mnt_mq}/etc/yum.repos.d/
fi
buildah run ${ctr_mq} -- microdnf ${microdnf_opts} install \
java-1.8.0-openjdk-devel \
java \
which \
wget
buildah run $ctr_mq -- sh -c "cd /tmp && wget https://www-eu.apache.org/dist/maven/maven-3/3.6.0/binaries/apache-maven-3.6.0-bin.tar.gz"
tar xvf $mnt_mq/tmp/apache-maven-3.6.0-bin.tar.gz -C $mnt_mq/tmp/
mkdir -p $mnt_mq/usr/src/mymaven
cp pom.xml $mnt_mq/usr/src/mymaven/
cp -R src $mnt_mq/usr/src/mymaven/src
buildah run $ctr_mq -- sh -c "cd /usr/src/mymaven && export M2_HOME=/tmp/apache-maven-3.6.0 && export M2=\$M2_HOME/bin && export PATH=\$M2:\$PATH && mvn --version && mvn dependency:go-offline install && mvn --offline install"
mkdir -p $mnt_mq/opt/app
cp $mnt_mq/usr/src/mymaven/target/*.jar $mnt_mq/opt/app/
cp $mnt_mq/usr/src/mymaven/target/lib/*.jar $mnt_mq/opt/app/
###############################################################################
# Post install tidy up
###############################################################################
rm -rf $mnt_mq/tmp/*
rm -rf $mnt_mq/usr/src/mymaven
# Clean up cached files
buildah run ${ctr_mq} -- microdnf ${microdnf_opts} clean all
rm -rf ${mnt_mq}/etc/yum.repos.d/*
###############################################################################
# Contain image finalization
###############################################################################
buildah config \
--os linux \
--label architecture=amd64 \
--label name="${imagename%:*}" \
--cmd "" \
--entrypoint '["java", "-classpath", "/opt/app/*", "org.junit.platform.console.ConsoleLauncher", "-p", "com.ibm.mqcontainer.test", "--details", "verbose"]' \
$ctr_mq
buildah unmount $ctr_mq
buildah commit $ctr_mq $imagename
buildah rm $ctr_mq

Some files were not shown because too many files have changed in this diff Show More