Don't use setuid on chkmq*

Also add new tests for chkmqhealthy and privileges
This commit is contained in:
Arthur Barr
2020-11-02 12:03:12 +00:00
parent 4fa7714361
commit 95be35d246
5 changed files with 87 additions and 30 deletions

View File

@@ -97,7 +97,7 @@ COPY web /etc/mqm/web
COPY etc/mqm/*.tpl /etc/mqm/ COPY etc/mqm/*.tpl /etc/mqm/
RUN chmod ug+x /usr/local/bin/runmqserver \ RUN chmod ug+x /usr/local/bin/runmqserver \
&& chown 1001:root /usr/local/bin/*mq* \ && chown 1001:root /usr/local/bin/*mq* \
&& chmod ug+xs /usr/local/bin/chkmq* \ && chmod ug+x /usr/local/bin/chkmq* \
&& chown -R 1001:root /etc/mqm/* \ && chown -R 1001:root /etc/mqm/* \
&& install --directory --mode 2775 --owner 1001 --group root /run/runmqserver \ && install --directory --mode 2775 --owner 1001 --group root /run/runmqserver \
&& touch /run/termination-log \ && touch /run/termination-log \

View File

@@ -37,7 +37,9 @@ MQ_ARCHIVE_DEV ?= $(MQ_VERSION)-IBM-MQ-Advanced-for-Developers-Non-Install-$(MQ_
# MQ_SDK_ARCHIVE specifies the archive to use for building the golang programs. Defaults vary on developer or advanced. # MQ_SDK_ARCHIVE specifies the archive to use for building the golang programs. Defaults vary on developer or advanced.
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 ?=
# Timeout for the Docker tests
TEST_TIMEOUT_DOCKER ?= 30m
# MQ_IMAGE_ADVANCEDSERVER is the name of the built MQ Advanced image # MQ_IMAGE_ADVANCEDSERVER is the name of the built MQ Advanced image
MQ_IMAGE_ADVANCEDSERVER ?=ibm-mqadvanced-server MQ_IMAGE_ADVANCEDSERVER ?=ibm-mqadvanced-server
# MQ_IMAGE_DEVSERVER is the name of the built MQ Advanced for Developers image # MQ_IMAGE_DEVSERVER is the name of the built MQ Advanced for Developers image
@@ -73,7 +75,7 @@ MQ_ARCHIVE_DEV_TYPE=Linux
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=$(lastword $(subst /, ,$(subst :,-,$(BASE_IMAGE)))) BASE_IMAGE_TAG=$(lastword $(subst /, ,$(subst :,-,$(BASE_IMAGE))))
#BASE_IMAGE_TAG=$(subst /,-,$(subst :,-,$(BASE_IMAGE))) #BASE_IMAGE_TAG=$(subst /,-,$(subst :,-,$(BASE_IMAGE)))
@@ -183,7 +185,7 @@ test-unit:
test-advancedserver: test/docker/vendor test-advancedserver: test/docker/vendor
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) on $(shell docker --version)"$(END))) $(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) on $(shell docker --version)"$(END)))
docker inspect $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) 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) cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) EXPECTED_LICENSE=Production go test -parallel $(NUM_CPU) -timeout $(TEST_TIMEOUT_DOCKER) $(TEST_OPTS_DOCKER)
.PHONY: build-devjmstest .PHONY: build-devjmstest
build-devjmstest: build-devjmstest:
@@ -193,8 +195,9 @@ 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):$(MQ_TAG) on $(shell docker --version)"$(END))) $(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_DEVSERVER):$(MQ_TAG) on $(shell docker --version)"$(END)))
docker inspect $(MQ_IMAGE_DEVSERVER):$(MQ_TAG) 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) 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) -timeout $(TEST_TIMEOUT_DOCKER) -tags mqdev $(TEST_OPTS_DOCKER)
.PHONY: coverage .PHONY: coverage
coverage: coverage:

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2017, 2019 © Copyright IBM Corporation 2017, 2020
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,11 +36,11 @@ func queueManagerHealthy() (bool, error) {
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()
fmt.Printf("%s", out)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return false, err return false, err
} }
fmt.Printf("%s", out)
if !strings.Contains(string(out), "(RUNNING)") && !strings.Contains(string(out), "(RUNNING AS STANDBY)") && !strings.Contains(string(out), "(STARTING)") { if !strings.Contains(string(out), "(RUNNING)") && !strings.Contains(string(out), "(RUNNING AS STANDBY)") && !strings.Contains(string(out), "(STARTING)") {
return false, nil return false, nil
} }

View File

@@ -1,5 +1,5 @@
/* /*
© Copyright IBM Corporation 2017, 2019 © Copyright IBM Corporation 2017, 2020
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.
@@ -1433,3 +1433,44 @@ func TestTraceStrmqm(t *testing.T) {
t.Fatalf("No trace files found in trace directory /var/mqm/trace. RC=%d.", rc) t.Fatalf("No trace files found in trace directory /var/mqm/trace. RC=%d.", rc)
} }
} }
// utilTestHealthCheck is used by TestHealthCheck* to run a container with
// privileges enabled or disabled. Otherwise the same as the golden path tests.
func utilTestHealthCheck(t *testing.T, nonewpriv bool) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
}
hostConfig := getDefaultHostConfig(t, cli)
hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("no-new-privileges:%v", nonewpriv))
id := runContainerWithHostConfig(t, cli, &containerConfig, hostConfig)
defer cleanContainer(t, cli, id)
waitForReady(t, cli, id)
rc, out := execContainer(t, cli, id, "", []string{"chkmqhealthy"})
t.Log(out)
if rc != 0 {
t.Errorf("Expected chkmqhealthy to return with exit code 0; got \"%v\"", rc)
t.Logf("Output from chkmqhealthy:\n%v", out)
}
// Stop the container cleanly
stopContainer(t, cli, id)
}
// TestHealthCheckWithNoNewPrivileges tests golden path start/stop plus
// chkmqhealthy, when running in a container where no new privileges are
// allowed (i.e. setuid is disabled)
func TestHealthCheckWithNoNewPrivileges(t *testing.T) {
utilTestHealthCheck(t, true)
}
// TestHealthCheckWithNoNewPrivileges tests golden path start/stop plus
// chkmqhealthy when running in a container where new privileges are
// allowed (i.e. setuid is allowed)
// See https://github.com/ibm-messaging/mq-container/issues/428
func TestHealthCheckWithNewPrivileges(t *testing.T) {
utilTestHealthCheck(t, false)
}

View File

@@ -267,20 +267,8 @@ func generateRandomUID() string {
return fmt.Sprint(rand.Intn(max-min) + min) return fmt.Sprint(rand.Intn(max-min) + min)
} }
// runContainerWithPorts creates and starts a container, exposing the specified ports on the host. // getDefaultHostConfig creates a HostConfig and populates it with the defaults used in testing
// If no image is specified in the container config, then the image name is retrieved from the TEST_IMAGE func getDefaultHostConfig(t *testing.T, cli *client.Client) *container.HostConfig {
// environment variable.
func runContainerWithPorts(t *testing.T, cli *client.Client, containerConfig *container.Config, ports []int) string {
if containerConfig.Image == "" {
containerConfig.Image = imageName()
}
// Always run as a random user, unless the test has specified otherwise
if containerConfig.User == "" {
containerConfig.User = generateRandomUID()
}
// if coverage
containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov")
containerConfig.Env = append(containerConfig.Env, "EXIT_CODE_FILE="+getExitCodeFilename(t))
hostConfig := container.HostConfig{ hostConfig := container.HostConfig{
Binds: []string{ Binds: []string{
coverageBind(t), coverageBind(t),
@@ -299,6 +287,37 @@ func runContainerWithPorts(t *testing.T, cli *client.Client, containerConfig *co
} else { } else {
t.Logf("Detected MQ Advanced image - dropping all capabilities") t.Logf("Detected MQ Advanced image - dropping all capabilities")
} }
return &hostConfig
}
// runContainerWithHostConfig creates and starts a container, using the supplied HostConfig.
// Note that a default HostConfig can be created using getDefaultHostConfig.
func runContainerWithHostConfig(t *testing.T, cli *client.Client, containerConfig *container.Config, hostConfig *container.HostConfig) string {
if containerConfig.Image == "" {
containerConfig.Image = imageName()
}
// Always run as a random user, unless the test has specified otherwise
if containerConfig.User == "" {
containerConfig.User = generateRandomUID()
}
// if coverage
containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov")
containerConfig.Env = append(containerConfig.Env, "EXIT_CODE_FILE="+getExitCodeFilename(t))
networkingConfig := network.NetworkingConfig{}
t.Logf("Running 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)
return ctr.ID
}
// 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
// environment variable.
func runContainerWithPorts(t *testing.T, cli *client.Client, containerConfig *container.Config, ports []int) string {
hostConfig := getDefaultHostConfig(t, cli)
for _, p := range ports { for _, p := range ports {
port := nat.Port(fmt.Sprintf("%v/tcp", p)) port := nat.Port(fmt.Sprintf("%v/tcp", p))
hostConfig.PortBindings[port] = []nat.PortBinding{ hostConfig.PortBindings[port] = []nat.PortBinding{
@@ -307,14 +326,7 @@ func runContainerWithPorts(t *testing.T, cli *client.Client, containerConfig *co
}, },
} }
} }
networkingConfig := network.NetworkingConfig{} return runContainerWithHostConfig(t, cli, containerConfig, hostConfig)
t.Logf("Running 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)
return ctr.ID
} }
// runContainer creates and starts a container. If no image is specified in // runContainer creates and starts a container. If no image is specified in
@@ -526,6 +538,7 @@ func waitForContainer(t *testing.T, cli *client.Client, ID string, timeout time.
// execContainer runs a command in a running container, and returns the exit code and output // execContainer runs a command in a running container, and returns the exit code and output
func execContainer(t *testing.T, cli *client.Client, ID string, user string, cmd []string) (int, string) { func execContainer(t *testing.T, cli *client.Client, ID string, user string, cmd []string) (int, string) {
t.Logf("Running command: %v", cmd)
config := types.ExecConfig{ config := types.ExecConfig{
User: user, User: user,
Privileged: false, Privileged: false,