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
This commit is contained in:
Arthur Barr
2019-02-25 15:44:14 +00:00
parent 2dbee560fe
commit cc0f072908
35 changed files with 871 additions and 504 deletions

View File

@@ -36,7 +36,7 @@ jobs:
- BASE_IMAGE=ubuntu:16.04 - BASE_IMAGE=ubuntu:16.04
- DOCKER_DOWNGRADE="echo nothing to be done" - DOCKER_DOWNGRADE="echo nothing to be done"
- env: - env:
- BASE_IMAGE=centos:latest - BASE_IMAGE=centos:7
- DOCKER_DOWNGRADE="echo nothing to be done" - DOCKER_DOWNGRADE="echo nothing to be done"
- if: type IN (pull_request) OR tag IS present - if: type IN (pull_request) OR tag IS present
env: env:

View File

@@ -2,8 +2,12 @@
## vNext ## vNext
* 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 parameter for runmqsc START and STOP commands](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.pro.doc/q132310_.htm#q132310___ignstateparm) - From MQ version 9.1.1.0, any MQSC scripts included in the image should make use of the `IGNSTATE(YES)` parameter on any `START` and `STOP` commands. This allows for consistency when executing scripts multiple times (e.g. when a container is restarted) * [New IGNSTATE parameter for runmqsc START and STOP commands](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.pro.doc/q132310_.htm#q132310___ignstateparm) - From MQ version 9.1.1.0, any MQSC scripts included in the image should make use of the `IGNSTATE(YES)` parameter on any `START` and `STOP` commands. This allows for consistency when executing scripts multiple times (e.g. when a container is restarted)
* 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
## 9.1.1.0 (2018-11-30) ## 9.1.1.0 (2018-11-30)

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.
@@ -48,12 +48,15 @@ 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_PACKAGES
# The UID to use for the "mqm" user
ARG MQM_UID=999
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.
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
# 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 \
@@ -65,11 +68,17 @@ COPY NOTICES.txt /opt/mqm/licenses/notices-container.txt
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* \
&& 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
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"]

View File

@@ -77,9 +77,11 @@ endif
ifeq "$(findstring ubuntu,$(BASE_IMAGE))" "ubuntu" ifeq "$(findstring ubuntu,$(BASE_IMAGE))" "ubuntu"
MQ_ARCHIVE_TYPE=UBUNTU MQ_ARCHIVE_TYPE=UBUNTU
MQ_ARCHIVE_DEV_PLATFORM=ubuntu MQ_ARCHIVE_DEV_PLATFORM=ubuntu
MQM_UID=999
else else
MQ_ARCHIVE_TYPE=LINUX MQ_ARCHIVE_TYPE=LINUX
MQ_ARCHIVE_DEV_PLATFORM=linux MQ_ARCHIVE_DEV_PLATFORM=linux
MQM_UID=888
endif 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)" "x86_64"
@@ -149,6 +151,7 @@ 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) on $(shell docker --version)"$(END)))
docker inspect $(MQ_IMAGE_ADVANCEDSERVER)
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER) EXPECTED_LICENSE=Production go test -parallel $(NUM_CPU) $(TEST_OPTS_DOCKER) cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER) EXPECTED_LICENSE=Production go test -parallel $(NUM_CPU) $(TEST_OPTS_DOCKER)
.PHONY: build-devjmstest .PHONY: build-devjmstest
@@ -159,6 +162,7 @@ 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) on $(shell docker --version)"$(END)))
docker inspect $(MQ_IMAGE_DEVSERVER)
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_DEVSERVER) EXPECTED_LICENSE=Developer DEV_JMS_IMAGE=$(DEV_JMS_IMAGE) IBMJRE=true go test -parallel $(NUM_CPU) -tags mqdev $(TEST_OPTS_DOCKER) cd test/docker && TEST_IMAGE=$(MQ_IMAGE_DEVSERVER) EXPECTED_LICENSE=Developer DEV_JMS_IMAGE=$(DEV_JMS_IMAGE) IBMJRE=true go test -parallel $(NUM_CPU) -tags mqdev $(TEST_OPTS_DOCKER)
coverage: coverage:
@@ -210,6 +214,7 @@ define docker-build-mq
--build-arg IMAGE_REVISION="$(IMAGE_REVISION)" \ --build-arg IMAGE_REVISION="$(IMAGE_REVISION)" \
--build-arg IMAGE_SOURCE="$(IMAGE_SOURCE)" \ --build-arg IMAGE_SOURCE="$(IMAGE_SOURCE)" \
--build-arg IMAGE_TAG="$1" \ --build-arg IMAGE_TAG="$1" \
--build-arg MQM_UID=$(MQM_UID) \
--label IBM_PRODUCT_ID=$4 \ --label IBM_PRODUCT_ID=$4 \
--label IBM_PRODUCT_NAME=$5 \ --label IBM_PRODUCT_NAME=$5 \
--label IBM_PRODUCT_VERSION=$6 \ --label IBM_PRODUCT_VERSION=$6 \
@@ -226,7 +231,7 @@ docker-version:
.PHONY: build-advancedserver .PHONY: build-advancedserver
build-advancedserver: MQ_SDK_ARCHIVE=$(MQ_ARCHIVE) build-advancedserver: MQ_SDK_ARCHIVE=$(MQ_ARCHIVE)
build-advancedserver: downloads/$(MQ_ARCHIVE) docker-version build-golang-sdk-ex build-advancedserver: downloads/$(MQ_ARCHIVE) docker-version build-golang-sdk
$(info $(SPACER)$(shell printf $(TITLE)"Build $(MQ_IMAGE_ADVANCEDSERVER)"$(END))) $(info $(SPACER)$(shell printf $(TITLE)"Build $(MQ_IMAGE_ADVANCEDSERVER)"$(END)))
$(call docker-build-mq,$(MQ_IMAGE_ADVANCEDSERVER),Dockerfile-server,$(MQ_ARCHIVE),"4486e8c4cc9146fd9b3ce1f14a2dfc5b","IBM MQ Advanced",$(MQ_VERSION)) $(call docker-build-mq,$(MQ_IMAGE_ADVANCEDSERVER),Dockerfile-server,$(MQ_ARCHIVE),"4486e8c4cc9146fd9b3ce1f14a2dfc5b","IBM MQ Advanced",$(MQ_VERSION))
@@ -238,10 +243,10 @@ 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: MQ_PACKAGES=MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesAMS-*.rpm MQSeriesWeb-*.rpm
endif endif
build-devserver: MQ_SDK_ARCHIVE=$(MQ_ARCHIVE_DEV) build-devserver: MQ_SDK_ARCHIVE=$(MQ_ARCHIVE_DEV)
build-devserver: downloads/$(MQ_ARCHIVE_DEV) docker-version build-golang-sdk-ex build-devserver: downloads/$(MQ_ARCHIVE_DEV) docker-version build-golang-sdk
$(info $(shell printf $(TITLE)"Build $(MQ_IMAGE_DEVSERVER_BASE)"$(END))) $(info $(shell printf $(TITLE)"Build $(MQ_IMAGE_DEVSERVER_BASE)"$(END)))
$(call docker-build-mq,$(MQ_IMAGE_DEVSERVER_BASE),Dockerfile-server,$(MQ_ARCHIVE_DEV),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)",$(MQ_VERSION)) $(call docker-build-mq,$(MQ_IMAGE_DEVSERVER_BASE),Dockerfile-server,$(MQ_ARCHIVE_DEV),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)",$(MQ_VERSION))
$(DOCKER) build --tag $(MQ_IMAGE_DEVSERVER) --build-arg IMAGE_SOURCE="$(IMAGE_SOURCE)" --build-arg IMAGE_REVISION="$(IMAGE_REVISION)" --build-arg IMAGE_TAG="$(MQ_IMAGE_DEVSERVER)" --build-arg BASE_IMAGE=$(MQ_IMAGE_DEVSERVER_BASE) --build-arg BUILDER_IMAGE=$(MQ_IMAGE_GOLANG_SDK) --file incubating/mqadvanced-server-dev/Dockerfile . $(DOCKER) build --tag $(MQ_IMAGE_DEVSERVER) --build-arg IMAGE_SOURCE="$(IMAGE_SOURCE)" --build-arg IMAGE_REVISION="$(IMAGE_REVISION)" --build-arg IMAGE_TAG="$(MQ_IMAGE_DEVSERVER)" --build-arg BASE_IMAGE=$(MQ_IMAGE_DEVSERVER_BASE) --build-arg BUILDER_IMAGE=$(MQ_IMAGE_GOLANG_SDK) --build-arg MQM_UID=$(MQM_UID) --file incubating/mqadvanced-server-dev/Dockerfile .
.PHONY: build-advancedserver-cover .PHONY: build-advancedserver-cover
build-advancedserver-cover: docker-version build-advancedserver-cover: docker-version
@@ -264,12 +269,8 @@ build-sdk-ex: 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)) $(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))
.PHONY: build-golang-sdk .PHONY: build-golang-sdk
build-golang-sdk: downloads/$(MQ_SDK_ARCHIVE) build-golang-sdk-ex build-golang-sdk:
$(DOCKER) build -t $(MQ_IMAGE_GOLANG_SDK) -f incubating/mq-golang-sdk/Dockerfile .
.PHONY: build-golang-sdk-ex
build-golang-sdk-ex: docker-version build-sdk-ex
$(DOCKER) build --build-arg BASE_IMAGE=$(MQ_IMAGE_SDK) -t $(MQ_IMAGE_GOLANG_SDK) -f incubating/mq-golang-sdk/Dockerfile .
# $(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))
.PHONY: docker-pull .PHONY: docker-pull
docker-pull: docker-pull:

View File

@@ -0,0 +1,63 @@
/*
© 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 main
import (
"runtime"
"strings"
containerruntime "github.com/ibm-messaging/mq-container/internal/containerruntime"
"github.com/ibm-messaging/mq-container/internal/user"
)
func logContainerDetails() {
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()
if err == nil {
for k, v := range caps {
if len(v) > 0 {
log.Printf("Capabilities (%s set): %v", strings.ToLower(k), strings.Join(v, ","))
}
}
} 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())
}

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.
@@ -31,7 +31,7 @@ var log *logger.Logger
func setPassword(user string, password string) error { func setPassword(user string, password string) error {
// #nosec G204 // #nosec G204
cmd := exec.Command("chpasswd") cmd := exec.Command("sudo", "chpasswd")
stdin, err := cmd.StdinPipe() stdin, err := cmd.StdinPipe()
if err != nil { if err != nil {
return err return err
@@ -41,9 +41,10 @@ func setPassword(user string, password string) error {
if err != nil { if err != nil {
log.Errorf("Error closing password stdin: %v", err) log.Errorf("Error closing password stdin: %v", err)
} }
_, _, err = command.RunCmd(cmd) out, _, err := command.RunCmd(cmd)
if err != nil { if err != nil {
return err // 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
@@ -93,16 +94,16 @@ func configureWeb(qmName string) error {
} }
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)
} }
@@ -115,6 +116,9 @@ func doMain() error {
logTermination(err) logTermination(err)
return err return err
} }
logContainerDetails()
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)
@@ -170,7 +174,7 @@ func main() {
} else { } else {
// Replace this process with runmqserver // Replace this process with runmqserver
// #nosec G204 // #nosec G204
err = syscall.Exec("/usr/local/bin/runmqserver", []string{"runmqserver"}, os.Environ()) err = syscall.Exec("/usr/local/bin/runmqserver", []string{"runmqserver", "-dev"}, os.Environ())
if err != nil { if err != nil {
log.Errorf("Error replacing this process with runmqserver: %v", err) 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.
@@ -36,7 +36,7 @@ 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) {
err = os.MkdirAll(dir, 0660) err = os.MkdirAll(dir, 0770)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
return err return 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.
@@ -36,15 +36,15 @@ var log *logger.Logger
var collectDiagOnFail = false 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)
} }

View File

@@ -0,0 +1,79 @@
/*
© 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 main
import (
"fmt"
"runtime"
"strings"
containerruntime "github.com/ibm-messaging/mq-container/internal/containerruntime"
"github.com/ibm-messaging/mq-container/internal/user"
)
func logContainerDetails() 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()
if err == nil {
for k, v := range caps {
if len(v) > 0 {
log.Printf("Capabilities (%s set): %v", strings.ToLower(k), strings.Join(v, ","))
}
}
}
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)
}
}
}
}
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.
@@ -30,10 +30,11 @@ import (
) )
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 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() flag.Parse()
// Configure the logger so we can output messages
name, nameErr := name.GetQueueManagerName() name, nameErr := name.GetQueueManagerName()
mf, err := configureLogger(name) mf, err := configureLogger(name)
if err != nil { if err != nil {
@@ -44,7 +45,7 @@ func doMain() error {
// Check whether they only want debug info // Check whether they only want debug info
if *infoFlag { if *infoFlag {
logVersionInfo() logVersionInfo()
logConfig() logContainerDetails()
return nil return nil
} }
@@ -81,16 +82,12 @@ func doMain() error {
// Enable diagnostic collecting on failure // Enable diagnostic collecting on failure
collectDiagOnFail = true collectDiagOnFail = true
err = verifyCurrentUser() if *devFlag == false {
if err != nil { err = logContainerDetails()
logTermination(err) if err != nil {
return err logTermination(err)
} return err
}
err = logConfig()
if err != nil {
logTermination(err)
return err
} }
err = createVolume("/mnt/mqm") err = createVolume("/mnt/mqm")
@@ -104,6 +101,11 @@ func doMain() error {
return err return err
} }
// If init flag is set, exit now
if *initFlag {
return nil
}
// Print out versioning information // Print out versioning information
logVersionInfo() logVersionInfo()

View File

@@ -1,157 +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"
"runtime"
"strings"
"github.com/genuinetools/amicontained/container"
)
func logContainerRuntime() {
r, err := container.DetectRuntime()
if err != nil {
log.Printf("Failed to get container runtime: %v", err)
return
}
log.Printf("Container runtime: %v", r)
}
func logBaseImage() {
buf, err := ioutil.ReadFile("/etc/os-release")
if err != nil {
log.Printf("Failed to read /etc/os-release: %v", err)
return
}
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
}
}
}
}
// logCapabilities logs the Linux capabilities (e.g. setuid, setgid). See https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
func logCapabilities() {
caps, err := container.Capabilities()
if err != nil {
log.Printf("Failed to get container capabilities: %v", err)
return
}
for k, v := range caps {
if len(v) > 0 {
log.Printf("Capabilities (%s set): %v", strings.ToLower(k), strings.Join(v, ","))
}
}
}
// logSeccomp logs the seccomp enforcing mode, which affects which kernel calls can be made
func logSeccomp() {
s, err := container.SeccompEnforcingMode()
if err != nil {
log.Printf("Failed to get container SeccompEnforcingMode: %v", err)
return
}
log.Printf("seccomp enforcing mode: %v", s)
}
// 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() {
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)
}
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 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/mqm") {
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

@@ -119,7 +119,7 @@ func configureQueueManager() error {
// Run the command and wait for completion // Run the command and wait for completion
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { if err != nil {
log.Errorf("Error running MQSC file %v: %v", file.Name(), err) log.Errorf("Error running MQSC file %v (%v):\n\t%v", file.Name(), err, strings.Replace(string(out), "\n", "\n\t", -1))
return err return err
} }
// Print the runmqsc output, adding tab characters to make it more readable as part of the log // Print the runmqsc output, adding tab characters to make it more readable as part of the log

View File

@@ -1,137 +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/user"
"strings"
"github.com/ibm-messaging/mq-container/internal/command"
)
const groupName string = "supplgrp"
func verifyCurrentUser() error {
log.Debug("Verifying current user information")
curUser, err := user.Current()
if err != nil {
return err
}
log.Debugf("Detected current user as: %v+", curUser)
if curUser.Username == "mqm" {
// Not supported yet
return fmt.Errorf("Container is running as mqm user which is not supported. Please run this container as root")
} else if curUser.Username == "root" {
// We're running as root so need to check for supplementary groups.
// We can't use the golang User.GroupIDs as it doesn't seem to detect container supplementary groups..
groups, err := getCurrentUserGroups()
for _, e := range groups {
_, _, testGroup := command.Run("getent", "group", e)
if testGroup != nil {
log.Printf("Group %s does not exist on the system... Adding to system and MQM user", e)
_, _, err = command.Run("groupadd", "-g", e, groupName)
if err != nil {
log.Errorf("Failed to create group %s as %s", e, groupName)
return err
}
_, _, err = command.Run("usermod", "-aG", groupName, "mqm")
if err != nil {
log.Errorf("Failed to add group %s(%s) to the mqm user.", groupName, e)
return err
}
}
}
} else {
// We're running as an unknown user...
return fmt.Errorf("Container is running as %s user which is not supported. Please run this container as root", curUser.Username)
}
return nil
}
func logUser() {
u, usererr := user.Current()
if usererr == nil {
g, err := getCurrentUserGroups()
if err != nil && len(g) == 0 {
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 supplementary groups %v", u.Uid, u.Name, u.Gid, strings.Join(g, ","))
}
}
if usererr == nil && u.Username != "mqm" {
mqm, err := user.Lookup("mqm")
// Need to print out mqm user details as well.
g, err := getUserGroups(mqm)
if err != nil && len(g) == 0 {
log.Printf("MQM user ID %v (%v) has primary group %v", mqm.Uid, "mqm", mqm.Gid)
} else {
// Look for the primary group in the list of group IDs
for i, v := range g {
if v == mqm.Gid {
// Remove the element from the slice
g = append(g[:i], g[i+1:]...)
}
}
log.Printf("MQM user ID %v (%v) has primary group %v, and supplementary groups %v", mqm.Uid, "mqm", mqm.Gid, strings.Join(g, ","))
}
}
}
func getCurrentUserGroups() ([]string, error) {
var nilArray []string
out, _, err := command.Run("id", "--groups")
if err != nil {
log.Debug("Unable to get current user groups")
return nilArray, err
}
out = strings.TrimSpace(out)
if out == "" {
// we don't have any groups?
return nilArray, fmt.Errorf("Unable to determine groups for current user")
}
groups := strings.Split(out, " ")
return groups, nil
}
func getUserGroups(usr *user.User) ([]string, error) {
var nilArray []string
out, _, err := command.Run("id", "--groups", usr.Uid)
if err != nil {
log.Debugf("Unable to get user %s groups", usr.Uid)
return nilArray, err
}
out = strings.TrimSpace(out)
if out == "" {
// we don't have any groups?
return nilArray, fmt.Errorf("Unable to determine groups for user %s", usr.Uid)
}
groups := strings.Split(out, " ")
return groups, nil
}

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.
@@ -22,7 +22,9 @@ import (
"io" "io"
"os" "os"
"os/exec" "os/exec"
"os/user"
"path/filepath" "path/filepath"
"strconv"
"syscall" "syscall"
"github.com/ibm-messaging/mq-container/internal/command" "github.com/ibm-messaging/mq-container/internal/command"
@@ -42,12 +44,23 @@ func startWebServer() error {
// Take all current environment variables, and add the app password // Take all current environment variables, and add the app password
cmd.Env = append(os.Environ(), "MQ_APP_PASSWORD=passw0rd") cmd.Env = append(os.Environ(), "MQ_APP_PASSWORD=passw0rd")
} }
cmd.SysProcAttr = &syscall.SysProcAttr{}
uid, gid, err := command.LookupMQM() uid, gid, err := command.LookupMQM()
if err != nil { if err != nil {
return err return err
} }
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} 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) 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))

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. On the Ubuntu-based image, this uses the UID and GID of 999. On the Red Hat Enterprise Linux image, it uses the 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.1.0-x86_64-ubuntu-16.04
```
The MQ Advanced for Developers image does requires 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.1.0-x86_64-ubuntu-16.04
```
### 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

@@ -66,20 +66,20 @@ The following is an *example* `Dockerfile` for creating your own pre-configured
```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://developer.ibm.com/messaging/2018/10/01/archives-getting-going-without-turning-off-ibm-mq-security/), 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 ```mqsc
DEFINE CHANNEL(PASSWORD.SVRCONN) CHLTYPE(SVRCONN) REPLACE DEFINE QLOCAL(MY.QUEUE.1) REPLACE
SET CHLAUTH(PASSWORD.SVRCONN) TYPE(BLOCKUSER) USERLIST('nobody') DESCR('Allow privileged users on this channel') DEFINE QLOCAL(MY.QUEUE.2) REPLACE
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)
``` ```
The file `20-config.mqsc` should be saved into the same directory as the `Dockerfile`. The file `20-config.mqsc` should be saved into the same directory as the `Dockerfile`.

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.
@@ -20,9 +20,11 @@ ARG MQ_URL=https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messag
# 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,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.
@@ -20,6 +20,8 @@ ARG MQ_URL=https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messag
# The MQ packages to install # The MQ packages to install
ARG MQ_PACKAGES="ibmmq-explorer" ARG MQ_PACKAGES="ibmmq-explorer"
ARG MQM_UID=999
RUN export DEBIAN_FRONTEND=noninteractive \ RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \ && apt-get update \
&& apt-get install -y \ && apt-get install -y \
@@ -27,7 +29,7 @@ RUN export DEBIAN_FRONTEND=noninteractive \
libxtst6 libxtst6
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 $MQM_UID \
&& install-mq.sh && install-mq.sh
ENV LANG=en_US.UTF-8 ENV LANG=en_US.UTF-8

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,22 +12,10 @@
# 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.1.1.0-x86_64-ubuntu-16.04 FROM golang:1.10
FROM $BASE_IMAGE # Install the MQ redistributable client (including header files) into the Go builder image
RUN mkdir -p /opt/mqm \
COPY incubating/mq-golang-sdk/install-golang.sh /usr/local/bin && curl -L https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqdev/redist/9.1.1.0-IBM-MQC-Redist-LinuxX64.tar.gz | tar -xz -C /opt/mqm
ENV CGO_CFLAGS="-I/opt/mqm/inc/" \
ENV GO_VERSION=1.10 CGO_LDFLAGS_ALLOW="-Wl,-rpath.*"
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

@@ -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.
@@ -23,6 +23,8 @@ ARG MQ_URL
# The packages to install in install-mq.sh # The packages to install in install-mq.sh
ARG MQ_PACKAGES ARG MQ_PACKAGES
ARG MQM_UID=999
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,6 @@ 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

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.
@@ -43,6 +43,20 @@ ENV MQ_DEV=true
# Default administrator password # Default administrator password
ENV MQ_ADMIN_PASSWORD=passw0rd ENV MQ_ADMIN_PASSWORD=passw0rd
ARG MQM_UID=999
USER root
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 ## Add admin and app users, and set a default password for admin
RUN useradd admin -G mqm \ RUN useradd admin -G mqm \
&& groupadd mqclient \ && groupadd mqclient \
@@ -55,12 +69,17 @@ RUN mkdir -p /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/runmqserver /usr/local/bin/
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqdevserver /usr/local/bin/ COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqdevserver /usr/local/bin/
# Copy template files # Copy template files
COPY incubating/mqadvanced-server-dev/*.tpl /etc/mqm/ COPY --chown=mqm:mqm incubating/mqadvanced-server-dev/*.tpl /etc/mqm/
# Copy web XML files for default developer configuration # Copy web XML files for default developer configuration
COPY incubating/mqadvanced-server-dev/web /etc/mqm/web COPY --chown=mqm:mqm incubating/mqadvanced-server-dev/web /etc/mqm/web
RUN chmod +x /usr/local/bin/runmq*
RUN chmod +x /usr/local/bin/runmq* \
&& install --directory --mode 0775 --owner mqm --group root /run/runmqdevserver
EXPOSE 9443 EXPOSE 9443
USER $MQM_UID
ENTRYPOINT ["runmqdevserver"] ENTRYPOINT ["runmqdevserver"]

View File

@@ -0,0 +1,32 @@
#!/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 && RHEL=true || RHEL=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 ($RHEL); then
yum -y install sudo
yum -y clean all
rm -rf /var/cache/yum/*
fi

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,6 +18,8 @@
# Fail on any non-zero return code # Fail on any non-zero return code
set -ex set -ex
mqm_uid=${1:-999}
test -f /usr/bin/yum && RHEL=true || RHEL=false test -f /usr/bin/yum && RHEL=true || RHEL=false
test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false
@@ -102,10 +104,8 @@ $UBUNTU && apt-get purge -y \
$UBUNTU && apt-get autoremove -y $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 -aG mqm root usermod -aG mqm root
# Find directory containing .deb files # Find directory containing .deb files
@@ -153,16 +153,18 @@ $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
# 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

View File

@@ -0,0 +1,120 @@
/*
© 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 runtime
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 logUser() {
// 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, ","))
// }
// }
// }
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")
}

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 runtime
import ( import (
"fmt"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@@ -101,24 +99,28 @@ 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 // log.Printf("WARNING: detected %v has unknown filesystem type %x", path, statfs.Type)
} }
switch t { return t, nil
}
// SupportedFilesystem returns true if the supplied filesystem type is supported for MQ data
func SupportedFilesystem(fsType string) bool {
switch fsType {
case "aufs", "overlayfs", "tmpfs": case "aufs", "overlayfs", "tmpfs":
return fmt.Errorf("%v uses unsupported filesystem type: %v", path, t) return false
default: default:
log.Printf("Detected %v has filesystem type '%v'", path, t) return true
return 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 runtime
// 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

@@ -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)

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

@@ -0,0 +1,82 @@
/*
© 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 == "" {
// we don't have any groups?
return nilArray, fmt.Errorf("Unable to determine groups for current user")
}
groups := strings.Split(out, " ")
return groups, nil
}

View File

@@ -36,6 +36,7 @@ IMAGE_SOURCE=${IMAGE_SOURCE:="Not Applicable"}
# Run the build in a container # 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 ":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 \ podman run \
--volume ${PWD}:/opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/:Z \ --volume ${PWD}:/opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/:Z \
--env IMAGE_REVISION="$IMAGE_REVISION" \ --env IMAGE_REVISION="$IMAGE_REVISION" \

View File

@@ -34,6 +34,8 @@ readonly mnt_mq=$2
readonly archive=$3 readonly archive=$3
readonly mq_packages=$4 readonly mq_packages=$4
readonly dir_extract=/tmp/extract readonly dir_extract=/tmp/extract
readonly mqm_uid=888
readonly mqm_gid=888
if [ ! -d ${dir_extract}/MQServer ]; then if [ ! -d ${dir_extract}/MQServer ]; then
mkdir -p ${dir_extract} mkdir -p ${dir_extract}
@@ -42,9 +44,6 @@ if [ ! -d ${dir_extract}/MQServer ]; then
echo Extracting finished echo Extracting finished
fi fi
# If MQ_PACKAGES isn't specifically set, then choose a valid set of defaults
# Accept the MQ license # 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 buildah run --user root --volume ${dir_extract}:/mnt/mq-download:Z $ctr_mq -- /mnt/mq-download/MQServer/mqlicense.sh -text_only -accept
@@ -63,13 +62,20 @@ find $mnt_mq/opt/mqm -name '*.tar.gz' -delete
buildah run $ctr_mq -- /opt/mqm/bin/setmqinst -p /opt/mqm -i buildah run $ctr_mq -- /opt/mqm/bin/setmqinst -p /opt/mqm -i
mkdir -p $mnt_mq/run/runmqserver mkdir -p $mnt_mq/run/runmqserver
chown 888:888 $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 # Remove the directory structure under /var/mqm which was created by the installer
rm -rf $mnt_mq/var/mqm rm -rf $mnt_mq/var/mqm
# Create the mount point for volumes # Create the mount point for volumes, ensuring MQ has permissions to all directories
mkdir -p $mnt_mq/mnt/mqm 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 # Create a symlink for /var/mqm -> /mnt/mqm/data
buildah run --user root $ctr_mq -- ln -s /mnt/mqm/data /var/mqm buildah run --user root $ctr_mq -- ln -s /mnt/mqm/data /var/mqm

View File

@@ -88,13 +88,17 @@ buildah run ${ctr_mq} -- microdnf ${microdnf_opts} install \
util-linux \ util-linux \
which 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 # Clean up cached files
buildah run ${ctr_mq} -- microdnf ${microdnf_opts} clean all buildah run ${ctr_mq} -- microdnf ${microdnf_opts} clean all
rm -rf ${mnt_mq}/etc/yum.repos.d/* 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 -- groupadd --system --gid ${mqm_gid} mqm
buildah run --user root $ctr_mq -- useradd --system --uid ${mqm_uid} --gid mqm mqm buildah run --user root $ctr_mq -- useradd --system --uid ${mqm_uid} --gid mqm --groups 0 mqm
buildah run --user root $ctr_mq -- usermod -aG root mqm
buildah run --user root $ctr_mq -- usermod -aG mqm root buildah run --user root $ctr_mq -- usermod -aG mqm root
# Install MQ server packages into the MQ builder image # Install MQ server packages into the MQ builder image
@@ -109,6 +113,11 @@ install --mode 0750 --owner ${mqm_uid} --group 0 ./build/runmqserver ${mnt_mq}/u
install --mode 6750 --owner ${mqm_uid} --group 0 ./build/chk* ${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 --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
############################################################################### ###############################################################################
# Final Buildah commands # Final Buildah commands
############################################################################### ###############################################################################
@@ -145,7 +154,7 @@ buildah config \
--env LANG=en_US.UTF-8 \ --env LANG=en_US.UTF-8 \
--env LOG_FORMAT=basic \ --env LOG_FORMAT=basic \
--entrypoint runmqserver \ --entrypoint runmqserver \
--user root \ --user ${mqm_uid} \
$ctr_mq $ctr_mq
buildah unmount $ctr_mq buildah unmount $ctr_mq
buildah commit $ctr_mq $tag buildah commit $ctr_mq $tag

View File

@@ -53,7 +53,12 @@ fi
readonly tag=$2 readonly tag=$2
readonly version=$3 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 # 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 -- useradd --gid mqm admin
@@ -61,17 +66,24 @@ 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 -- useradd --gid mqclient app
buildah run --user root $ctr_mq -- bash -c "echo admin:passw0rd | chpasswd" buildah run --user root $ctr_mq -- bash -c "echo admin:passw0rd | chpasswd"
mkdir -p $mnt_mq/run/runmqdevserver mkdir --parents $mnt_mq/run/runmqdevserver
chown 888:888 $mnt_mq/run/runmqdevserver chown ${mqm_uid}:${mqm_gid} $mnt_mq/run/runmqdevserver
# Copy runmqdevserver program # Copy runmqdevserver program
install --mode 0750 --owner 888 --group 888 ./build/runmqdevserver ${mnt_mq}/usr/local/bin/ 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 # Copy template files
cp incubating/mqadvanced-server-dev/*.tpl ${mnt_mq}/etc/mqm/ cp ./incubating/mqadvanced-server-dev/*.tpl ${mnt_mq}/etc/mqm/
# Copy web XML files for default developer configuration # Copy web XML files for default developer configuration
cp -R incubating/mqadvanced-server-dev/web ${mnt_mq}/etc/mqm/web mkdir --parents ${mnt_mq}/etc/mqm/web
cp --recursive ./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 # Final Buildah commands
@@ -102,7 +114,7 @@ buildah config \
--env MQ_ADMIN_PASSWORD=passw0rd \ --env MQ_ADMIN_PASSWORD=passw0rd \
--env MQ_DEV=true \ --env MQ_DEV=true \
--entrypoint runmqdevserver \ --entrypoint runmqdevserver \
--user root \ --user ${mqm_uid} \
$ctr_mq $ctr_mq
buildah unmount $ctr_mq buildah unmount $ctr_mq
buildah commit $ctr_mq $tag buildah commit $ctr_mq $tag

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.
@@ -114,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)
} }

View File

@@ -47,11 +47,11 @@ 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, 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)
} }
func TestLicenseView(t *testing.T) { func TestLicenseView(t *testing.T) {
@@ -65,7 +65,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, 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)
} }
@@ -164,12 +164,12 @@ func TestSecurityVulnerabilitiesRedHat(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
mnt = strings.TrimSpace(mnt) mnt = strings.TrimSpace(mnt)
_, _, err = command.Run("bash", "-c", "cp /etc/yum.repos.d/* "+ filepath.Join(mnt, "/etc/yum.repos.d/")) _, _, err = command.Run("bash", "-c", "cp /etc/yum.repos.d/* "+filepath.Join(mnt, "/etc/yum.repos.d/"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
out, ret, _ := command.Run("bash", "-c", "yum --installroot="+mnt+" updateinfo list sec | grep /Sec") out, ret, _ := command.Run("bash", "-c", "yum --installroot="+mnt+" updateinfo list sec | grep /Sec")
if ret != 1{ if ret != 1 {
t.Errorf("Expected no vulnerabilities, found the following:\n%v", out) t.Errorf("Expected no vulnerabilities, found the following:\n%v", out)
} }
} }
@@ -279,6 +279,70 @@ func TestNoVolumeWithRestart(t *testing.T) {
waitForReady(t, cli, id) waitForReady(t, cli, id)
} }
// TestVolumeRequiresRoot tests the case where only the root user can write
// to the persistent volume. In this case, an "init container" is needed,
// 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()
if err != nil {
t.Fatal(err)
}
vol := createVolume(t, cli)
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 {
t.Fatal(err)
}
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, 10*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` // TestCreateQueueManagerFail causes a failure of `crtmqm`
func TestCreateQueueManagerFail(t *testing.T) { func TestCreateQueueManagerFail(t *testing.T) {
t.Parallel() t.Parallel()
@@ -286,24 +350,31 @@ func TestCreateQueueManagerFail(t *testing.T) {
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\nexit 999' > /opt/mqm/bin/crtmqm
RUN chown mqm:mqm /opt/mqm/bin/crtmqm
RUN chmod 6550 /opt/mqm/bin/crtmqm
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"}, 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`
@@ -313,24 +384,31 @@ func TestStartQueueManagerFail(t *testing.T) {
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, 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)
} }
// TestVolumeUnmount runs a queue manager with a volume, and then forces an // TestVolumeUnmount runs a queue manager with a volume, and then forces an
@@ -430,7 +508,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)
@@ -461,7 +545,13 @@ func TestInvalidMQSC(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 mqscTest.mqsc /etc/mqm/", imageName())}, {"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"}, {"mqscTest.mqsc", "DEFINE INVALIDLISTENER('TEST.LISTENER.TCP') TRPTYPE(TCP) PORT(1414) CONTROL(QMGR) REPLACE"},
} }
tag := createImage(t, cli, files) tag := createImage(t, cli, files)
@@ -473,11 +563,11 @@ func TestInvalidMQSC(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, 60*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)
} }
// TestReadiness creates a new image with large amounts of MQSC in, to // TestReadiness creates a new image with large amounts of MQSC in, to
@@ -497,7 +587,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)
@@ -656,11 +752,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, 5*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

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.
@@ -59,6 +59,18 @@ func imageNameDevJMS() string {
return image return image
} }
func baseImage(t *testing.T, cli *client.Client) string {
rc, out := runContainerOneShot(t, cli, "bash", "-c", "cat /etc/*release | grep \"^ID=\"")
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]
}
// 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,46 +136,31 @@ 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")
} }
@@ -195,11 +192,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{
@@ -212,6 +208,22 @@ func cleanContainer(t *testing.T, cli *client.Client, ID string) {
} }
} }
// devImage returns true if the specified image is a developer image,
// determined by use of the MQ_ADMIN_PASSWORD or MQ_APP_PASSWORD
// environment variables
func devImage(t *testing.T, cli *client.Client, imageID string) bool {
i, _, err := cli.ImageInspectWithRaw(context.Background(), imageID)
if err != nil {
t.Fatal(err)
}
for _, e := range i.ContainerConfig.Env {
if strings.HasPrefix(e, "MQ_ADMIN_PASSWORD") || strings.HasPrefix(e, "MQ_APP_PASSWORD") {
return true
}
}
return false
}
// runContainerWithPorts creates and starts a container, exposing the specified ports on the host. // runContainerWithPorts creates and starts a container, exposing the specified ports on the host.
// If no image is specified in the container config, then the image name is retrieved from the TEST_IMAGE // If no image is specified in the container config, then the image name is retrieved from the TEST_IMAGE
// environment variable. // environment variable.
@@ -219,15 +231,35 @@ 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), // terminationBind(t),
}, },
PortBindings: nat.PortMap{}, PortBindings: nat.PortMap{},
CapDrop: []string{
"ALL",
},
}
if devImage(t, cli, containerConfig.Image) {
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")
}
} }
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 +286,49 @@ 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)", containerConfig.Image)
ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name())
if err != nil {
t.Fatal(err)
}
startContainer(t, cli, ctr.ID)
defer cleanContainer(t, cli, ctr.ID)
return waitForContainer(t, cli, ctr.ID, 10*time.Second), inspectLogs(t, cli, ctr.ID)
}
// 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())
if err != nil {
t.Fatal(err)
}
t.Logf("One shot container ID: %v", ctr.ID)
startContainer(t, cli, ctr.ID)
defer cleanContainer(t, cli, ctr.ID)
return waitForContainer(t, cli, ctr.ID, 10*time.Second), inspectLogs(t, cli, ctr.ID)
} }
func startContainer(t *testing.T, cli *client.Client, ID string) { func startContainer(t *testing.T, cli *client.Client, ID string) {
@@ -309,19 +377,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 +463,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 {