TLS and HTTPS configuration in default developer config

This commit is contained in:
Arthur Barr
2018-03-20 16:47:24 +00:00
parent 913465620b
commit ab3b44e84b
21 changed files with 849 additions and 42 deletions

View File

@@ -0,0 +1,136 @@
/*
© 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 (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/ibm-messaging/mq-container/internal/command"
)
type KeyStore struct {
Filename string
Password string
keyStoreType string
command string
}
// NewJKSKeyStore creates a new Java Key Store, managed by the runmqckm command
func NewJKSKeyStore(filename, password string) *KeyStore {
return &KeyStore{
Filename: filename,
Password: password,
keyStoreType: "jks",
command: "/opt/mqm/bin/runmqckm",
}
}
// NewCMSKeyStore creates a new MQ CMS Key Store, managed by the runmqakm command
func NewCMSKeyStore(filename, password string) *KeyStore {
return &KeyStore{
Filename: filename,
Password: password,
keyStoreType: "cms",
command: "/opt/mqm/bin/runmqakm",
}
}
// Create a key store, if it doesn't already exist
func (ks *KeyStore) Create() error {
_, err := os.Stat(ks.Filename)
if err != nil {
if os.IsNotExist(err) {
_, _, err := command.Run(ks.command, "-keydb", "-create", "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password, "-stash")
if err != nil {
return fmt.Errorf("error running \"%v -keydb -create\": %v", ks.command, err)
}
}
}
// TODO: Lookup value for MQM user here?
err = os.Chown(ks.Filename, 999, 999)
if err != nil {
log.Error(err)
return err
}
return nil
}
// Create a key stash, if it doesn't already exist
func (ks *KeyStore) CreateStash() error {
extension := filepath.Ext(ks.Filename)
stashFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".sth"
log.Debugf("TLS stash file: %v", stashFile)
_, err := os.Stat(stashFile)
if err != nil {
if os.IsNotExist(err) {
_, _, err := command.Run(ks.command, "-keydb", "-stashpw", "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password)
if err != nil {
return fmt.Errorf("error running \"%v -keydb -stashpw\": %v", ks.command, err)
}
}
return err
}
// TODO: Lookup value for MQM user here?
err = os.Chown(stashFile, 999, 999)
if err != nil {
log.Error(err)
return err
}
return nil
}
func (ks *KeyStore) Import(inputFile, password string) error {
_, _, err := command.Run(ks.command, "-cert", "-import", "-file", inputFile, "-pw", password, "-target", ks.Filename, "-target_pw", ks.Password, "-target_type", ks.keyStoreType)
if err != nil {
return fmt.Errorf("error running \"%v -cert -import\": %v", ks.command, err)
}
return nil
}
// GetCertificateLabels returns the labels of all certificates in the key store
func (ks *KeyStore) GetCertificateLabels() ([]string, error) {
out, _, err := command.Run(ks.command, "-cert", "-list", "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password)
if err != nil {
return nil, fmt.Errorf("error running \"%v -cert -list\": %v", ks.command, err)
}
scanner := bufio.NewScanner(strings.NewReader(out))
var labels []string
for scanner.Scan() {
s := scanner.Text()
if strings.HasPrefix(s, "-") || strings.HasPrefix(s, "*-") {
s := strings.TrimLeft(s, "-*")
labels = append(labels, strings.TrimSpace(s))
}
}
err = scanner.Err()
if err != nil {
return nil, err
}
return labels, nil
}
// RenameCertificate renames the specified certificate
func (ks *KeyStore) RenameCertificate(from, to string) error {
_, _, err := command.Run(ks.command, "-cert", "-rename", "-db", ks.Filename, "-pw", ks.Password, "-label", from, "-new_label", to)
if err != nil {
return fmt.Errorf("error running \"%v -cert -rename\": %v", ks.command, err)
}
return nil
}

View File

@@ -129,6 +129,19 @@ func doMain() error {
return err
}
name, err := name.GetQueueManagerName()
if err != nil {
logTerminationf("Error getting queue manager name: %v", err)
}
ks, set := os.LookupEnv("MQ_TLS_KEYSTORE")
if set {
err = configureTLS(name, ks, os.Getenv("MQ_TLS_PASSPHRASE"))
if err != nil {
logTerminationf("Error configuring TLS: %v", err)
return err
}
}
return nil
}

View File

@@ -27,7 +27,7 @@ func updateMQSC(appPasswordRequired bool) error {
} else {
checkClient = "ASQMGR"
}
const mqsc string = "/etc/mqm/dev.mqsc"
const mqsc string = "/etc/mqm/10-dev.mqsc"
if os.Getenv("MQ_DEV") == "true" {
const mqscTemplate string = mqsc + ".tpl"
// Re-configure channel if app password not set

140
cmd/runmqdevserver/tls.go Normal file
View File

@@ -0,0 +1,140 @@
/*
© Copyright IBM Corporation 2018
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"os"
"path/filepath"
)
func configureWebTLS(cms *KeyStore) error {
dir := "/run/runmqdevserver/tls"
ks := NewJKSKeyStore(filepath.Join(dir, "key.jks"), cms.Password)
ts := NewJKSKeyStore(filepath.Join(dir, "trust.jks"), cms.Password)
log.Debug("Creating key store")
err := ks.Create()
if err != nil {
return err
}
log.Debug("Creating trust store")
err = ts.Create()
if err != nil {
return err
}
log.Debug("Importing keys")
err = ks.Import(cms.Filename, cms.Password)
if err != nil {
return err
}
webConfigDir := "/etc/mqm/web/installations/Installation1/servers/mqweb"
tlsConfig := filepath.Join(webConfigDir, "tls.xml")
newTLSConfig := filepath.Join(webConfigDir, "tls-dev.xml")
err = os.Remove(tlsConfig)
if err != nil {
return err
}
err = os.Rename(newTLSConfig, tlsConfig)
if err != nil {
return err
}
return nil
}
func configureTLS(qmName string, inputFile string, passPhrase string) error {
log.Debug("Configuring TLS")
_, err := os.Stat(inputFile)
if err != nil {
return err
}
// TODO: Use a persisted file (on the volume) instead?
dir := "/run/runmqdevserver/tls"
keyFile := filepath.Join(dir, "key.kdb")
_, err = os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(dir, 0770)
if err != nil {
return err
}
err = os.Chown(dir, 999, 999)
if err != nil {
log.Debug(err)
return err
}
} else {
return err
}
}
cms := NewCMSKeyStore(keyFile, passPhrase)
err = cms.Create()
if err != nil {
return err
}
err = cms.CreateStash()
if err != nil {
return err
}
err = cms.Import(inputFile, passPhrase)
if err != nil {
return err
}
labels, err := cms.GetCertificateLabels()
if err != nil {
return err
}
if len(labels) == 0 {
return fmt.Errorf("unable to find certificate label")
}
log.Debugf("Renaming certificate from %v", labels[0])
const newLabel string = "devcert"
err = cms.RenameCertificate(labels[0], newLabel)
if err != nil {
return err
}
if os.Getenv("MQ_DEV") == "true" {
f, err := os.OpenFile("/etc/mqm/20-dev-tls.mqsc", os.O_WRONLY|os.O_CREATE, 0770)
if err != nil {
return err
}
defer f.Close()
// Change the Queue Manager's Key Repository to point at the new TLS key store
fmt.Fprintf(f, "ALTER QMGR SSLKEYR('%s')\n", filepath.Join(dir, "key"))
fmt.Fprintf(f, "ALTER QMGR CERTLABL('%s')\n", newLabel)
// Alter the DEV channels to use TLS
fmt.Fprintln(f, "ALTER CHANNEL('DEV.APP.SVRCONN') CHLTYPE(SVRCONN) SSLCIPH(TLS_RSA_WITH_AES_128_CBC_SHA256) SSLCAUTH(OPTIONAL)")
fmt.Fprintln(f, "ALTER CHANNEL('DEV.ADMIN.SVRCONN') CHLTYPE(SVRCONN) SSLCIPH(TLS_RSA_WITH_AES_128_CBC_SHA256) SSLCAUTH(OPTIONAL)")
}
err = configureWebTLS(cms)
if err != nil {
return err
}
return nil
}

View File

@@ -17,7 +17,10 @@ limitations under the License.
*/
package main
import "os"
import (
"os"
"path/filepath"
)
// postInit is run after /var/mqm is set up
// This version of postInit is only included as part of the MQ Advanced for Developers build
@@ -32,5 +35,26 @@ func postInit(name string) error {
startWebServer()
}()
}
dir := "/etc/mqm/tls"
keyFile := filepath.Join(dir, "key.kdb")
stashFile := filepath.Join(dir, "key.sth")
_, err := os.Stat(keyFile)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
_, err = os.Stat(stashFile)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
return nil
}

View File

@@ -19,6 +19,7 @@ package main
import (
"fmt"
"io"
"os"
"path/filepath"
@@ -41,6 +42,26 @@ func startWebServer() error {
return nil
}
// CopyFile copies the specified file
func CopyFile(src, dest string) error {
log.Debugf("Copying file %v to %v", src, dest)
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, 0770)
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
err = out.Close()
return err
}
func configureWebServer() error {
_, err := os.Stat("/opt/mqm/bin/strmqweb")
if err != nil {
@@ -76,7 +97,6 @@ func configureWebServer() error {
return err
}
}
if info.IsDir() {
if !exists {
err := os.MkdirAll(to, 0770)
@@ -84,7 +104,6 @@ func configureWebServer() error {
return err
}
}
log.Printf("Directory: %v --> %v", from, to)
} else {
if exists {
err := os.Remove(to)
@@ -92,13 +111,11 @@ func configureWebServer() error {
return err
}
}
// TODO: Permissions. Can't rely on them being set in Dockerfile
err := os.Link(from, to)
err := CopyFile(from, to)
if err != nil {
log.Debug(err)
return err
}
log.Printf("File: %v", from)
}
err = os.Chown(to, uid, gid)
if err != nil {

View File

@@ -32,9 +32,10 @@ DEFINE AUTHINFO('DEV.AUTHINFO') AUTHTYPE(IDPWOS) CHCKCLNT(REQDADM) CHCKLOCL(OPTI
ALTER QMGR CONNAUTH('DEV.AUTHINFO')
REFRESH SECURITY(*) TYPE(CONNAUTH)
* Developer channels (Application + Admin)
* Developer channels (Application + Admin)
DEFINE CHANNEL('DEV.ADMIN.SVRCONN') CHLTYPE(SVRCONN) REPLACE
DEFINE CHANNEL('DEV.APP.SVRCONN') CHLTYPE(SVRCONN) REPLACE
DEFINE CHANNEL('DEV.APP.SVRCONN') CHLTYPE(SVRCONN) MCAUSER('app') REPLACE
* Developer channel authentication rules
SET CHLAUTH('*') TYPE(ADDRESSMAP) ADDRESS('*') USERSRC(NOACCESS) DESCR('Back-stop rule - Blocks everyone') ACTION(REPLACE)
@@ -43,8 +44,7 @@ SET CHLAUTH('DEV.ADMIN.SVRCONN') TYPE(BLOCKUSER) USERLIST('nobody') DESCR('Allow
SET CHLAUTH('DEV.ADMIN.SVRCONN') TYPE(USERMAP) CLNTUSER('admin') USERSRC(CHANNEL) DESCR('Allows admin user to connect via ADMIN channel') ACTION(REPLACE)
* Developer authority records
SET AUTHREC PROFILE('DEV.AUTHINFO') GROUP('root') OBJTYPE(AUTHINFO) AUTHADD(CHG,DLT,DSP,INQ)
SET AUTHREC PROFILE('DEV.AUTHINFO') GROUP('mqm') OBJTYPE(AUTHINFO) AUTHADD(CHG,DLT,DSP,INQ)
SET AUTHREC PROFILE('self') GROUP('mqclient') OBJTYPE(QMGR) AUTHADD(CONNECT,INQ)
SET AUTHREC PROFILE('DEV.**') GROUP('mqclient') OBJTYPE(QUEUE) AUTHADD(BROWSE,GET,INQ,PUT)
SET AUTHREC PROFILE('DEV.**') GROUP('mqclient') OBJTYPE(TOPIC) AUTHADD(PUB,SUB)

View File

@@ -40,13 +40,17 @@ ENV MQ_ADMIN_PASSWORD=passw0rd
## Add admin and app users, and set a default password for admin
RUN useradd admin -G mqm \
&& groupadd mqclient \
&& useradd app -G mqclient,mqm \
&& useradd app -G mqclient \
&& echo admin:$MQ_ADMIN_PASSWORD | chpasswd
# Create a directory for runtime data from runmqserver
RUN mkdir -p /run/runmqdevserver \
&& chown mqm:mqm /run/runmqdevserver
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqserver /usr/local/bin/
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqdevserver /usr/local/bin/
# Copy template MQSC for default developer configuration
COPY incubating/mqadvanced-server-dev/dev.mqsc.tpl /etc/mqm/
COPY incubating/mqadvanced-server-dev/10-dev.mqsc.tpl /etc/mqm/
# Copy web XML files for default developer configuration
COPY incubating/mqadvanced-server-dev/web /etc/mqm/web
RUN chmod +x /usr/local/bin/runmq*

View File

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

View File

@@ -1 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<server>
<sslDefault sslRef="mqDefaultSSLConfig"/>
</server>

View File

@@ -18,14 +18,15 @@ limitations under the License.
package main
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"path/filepath"
"testing"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
)
func TestDevGoldenPath(t *testing.T) {
@@ -35,7 +36,12 @@ func TestDevGoldenPath(t *testing.T) {
t.Fatal(err)
}
containerConfig := container.Config{
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
Env: []string{
"LICENSE=accept",
"MQ_QMGR_NAME=qm1",
// TODO: Use default password (not set) here
"MQ_APP_PASSWORD=" + devAppPassword,
},
}
id := runContainer(t, cli, &containerConfig)
@@ -43,30 +49,74 @@ func TestDevGoldenPath(t *testing.T) {
waitForReady(t, cli, id)
waitForWebReady(t, cli, id)
timeout := time.Duration(30 * time.Second)
// Disable TLS verification (server uses a self-signed certificate by default,
// so verification isn't useful anyway)
tr := &http.Transport{
TLSClientConfig: &tls.Config{
t.Run("REST", func(t *testing.T) {
// Disable TLS verification (server uses a self-signed certificate by default,
// so verification isn't useful anyway)
testREST(t, cli, id, &tls.Config{
InsecureSkipVerify: true,
},
}
httpClient := http.Client{
Timeout: timeout,
Transport: tr,
}
url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", getWebPort(t, cli, id))
req, err := http.NewRequest("GET", url, nil)
req.SetBasicAuth("admin", "passw0rd")
resp, err := httpClient.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected HTTP status code %v from 'GET installation'; got %v", http.StatusOK, resp.StatusCode)
}
})
})
t.Run("JMS", func(t *testing.T) {
runJMSTests(t, cli, id, false)
})
// Stop the container cleanly
stopContainer(t, cli, id)
}
func TestDevTLS(t *testing.T) {
t.Parallel()
cli, err := client.NewEnvClient()
if err != nil {
t.Fatal(err)
}
const tlsPassPhrase string = "passw0rd"
containerConfig := container.Config{
Env: []string{
"LICENSE=accept",
"MQ_QMGR_NAME=qm1",
"MQ_APP_PASSWORD=" + devAppPassword,
"MQ_TLS_KEYSTORE=/var/tls/server.p12",
"MQ_TLS_PASSPHRASE=" + tlsPassPhrase,
"DEBUG=1",
},
Image: imageName(),
}
hostConfig := container.HostConfig{
Binds: []string{
coverageBind(t),
tlsDir(t) + ":/var/tls",
},
// Assign a random port for the web server on the host
// TODO: Don't do this for all tests
PortBindings: nat.PortMap{
"9443/tcp": []nat.PortBinding{
{
HostIP: "0.0.0.0",
},
},
},
}
networkingConfig := network.NetworkingConfig{}
ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name())
if err != nil {
t.Fatal(err)
}
defer cleanContainer(t, cli, ctr.ID)
startContainer(t, cli, ctr.ID)
waitForReady(t, cli, ctr.ID)
waitForWebReady(t, cli, ctr.ID)
t.Run("REST", func(t *testing.T) {
// Use the correct certificate for the HTTPS connection
cert := filepath.Join(tlsDir(t), "server.crt")
testREST(t, cli, ctr.ID, createTLSConfig(t, cert, tlsPassPhrase))
})
t.Run("JMS", func(t *testing.T) {
runJMSTests(t, cli, ctr.ID, true)
})
// Stop the container cleanly
stopContainer(t, cli, ctr.ID)
}

View File

@@ -18,14 +18,26 @@ limitations under the License.
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
)
const devAdminPassword string = "passw0rd"
const devAppPassword string = "passw0rd"
func waitForWebReady(t *testing.T, cli *client.Client, ID string) {
config := tls.Config{InsecureSkipVerify: true}
a := fmt.Sprintf("localhost:%s", getWebPort(t, cli, ID))
@@ -34,9 +46,101 @@ func waitForWebReady(t *testing.T, cli *client.Client, ID string) {
if err == nil {
conn.Close()
// Extra sleep to allow web apps to start
time.Sleep(3 * time.Second)
time.Sleep(5 * time.Second)
t.Log("MQ web server is ready")
return
}
time.Sleep(1 * time.Second)
}
}
// tlsDir returns the host directory where the test certificate(s) are located
func tlsDir(t *testing.T) string {
dir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
return filepath.Join(dir, "../tls")
}
// runJMSTests runs a container with a JMS client, which connects to the queue manager container with the specified ID
func runJMSTests(t *testing.T, cli *client.Client, ID string, tls bool) {
containerConfig := container.Config{
// -e MQ_PORT_1414_TCP_ADDR=9.145.14.173 -e MQ_USERNAME=app -e MQ_PASSWORD=passw0rd -e MQ_CHANNEL=DEV.APP.SVRCONN -e MQ_TLS_KEYSTORE=/tls/test.p12 -e MQ_TLS_PASSPHRASE=passw0rd -v /Users/arthurbarr/go/src/github.com/ibm-messaging/mq-container/test/tls:/tls msgtest
Env: []string{
"MQ_PORT_1414_TCP_ADDR=" + getIPAddress(t, cli, ID),
"MQ_USERNAME=app",
"MQ_PASSWORD=" + devAppPassword,
"MQ_CHANNEL=DEV.APP.SVRCONN",
},
Image: "msgtest",
}
if tls {
t.Log("Using TLS from JMS client")
containerConfig.Env = append(containerConfig.Env, []string{
"MQ_TLS_TRUSTSTORE=/var/tls/client-trust.jks",
"MQ_TLS_PASSPHRASE=passw0rd",
}...)
}
hostConfig := container.HostConfig{
Binds: []string{
coverageBind(t),
tlsDir(t) + ":/var/tls",
},
}
networkingConfig := network.NetworkingConfig{}
ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, strings.Replace(t.Name(), "/", "", -1))
if err != nil {
t.Fatal(err)
}
startContainer(t, cli, ctr.ID)
rc := waitForContainer(t, cli, ctr.ID, 10)
if rc != 0 {
t.Errorf("JUnit container failed with rc=%v", rc)
}
defer cleanContainer(t, cli, ctr.ID)
}
// createTLSConfig creates a tls.Config which trusts the specified certificate
func createTLSConfig(t *testing.T, certFile, password string) *tls.Config {
// Get the SystemCertPool, continue with an empty pool on error
certs, err := x509.SystemCertPool()
if err != nil {
t.Fatal(err)
}
// Read in the cert file
cert, err := ioutil.ReadFile(certFile)
if err != nil {
t.Fatal(err)
}
// Append our cert to the system pool
ok := certs.AppendCertsFromPEM(cert)
if !ok {
t.Fatal("No certs appended")
}
// Trust the augmented cert pool in our client
return &tls.Config{
InsecureSkipVerify: false,
RootCAs: certs,
}
}
func testREST(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config) {
httpClient := http.Client{
Timeout: time.Duration(30 * time.Second),
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
}
url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", getWebPort(t, cli, ID))
req, err := http.NewRequest("GET", url, nil)
req.SetBasicAuth("admin", devAdminPassword)
resp, err := httpClient.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected HTTP status code %v from 'GET installation'; got %v", http.StatusOK, resp.StatusCode)
}
}

6
test/messaging/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
.classpath
.gradle
.project
.settings
bin

38
test/messaging/Dockerfile Normal file
View File

@@ -0,0 +1,38 @@
# © 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.
###############################################################################
# Application build environment (Gradle)
###############################################################################
FROM gradle as builder
ENV GRADLE_OPTS="-Dorg.gradle.daemon=false"
# Change where Gradle stores its cache, so that it's not under a volume
# (and therefore gets cached by Docker)
ENV GRADLE_USER_HOME=/home/gradle/gradle
RUN mkdir -p $GRADLE_USER_HOME
COPY --chown=gradle build.gradle /app/
WORKDIR /app
# Download dependencies separately, so Docker caches them
RUN gradle download
# Copy source
COPY --chown=gradle src /app/src
# Run the main build
RUN gradle install
###############################################################################
# Application runtime (JRE only, no build environment)
###############################################################################
FROM ibmjava:sfj
COPY --from=builder /app/lib/*.jar /opt/app/
ENTRYPOINT ["java", "-classpath", "/opt/app/*", "org.junit.platform.console.ConsoleLauncher", "-p", "com.ibm.mqcontainer.test", "--details", "verbose"]

View File

@@ -0,0 +1,39 @@
# © 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.
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile group: 'com.ibm.mq', name: 'com.ibm.mq.allclient', version: '9.0.4.0'
compile "org.junit.jupiter:junit-jupiter-api:5.0.3"
runtime "org.junit.jupiter:junit-jupiter-engine:5.0.3"
runtime "org.junit.platform:junit-platform-console-standalone:1.0.3"
}
task download(type: Exec) {
configurations.runtime.files
commandLine 'echo', 'Downloaded all dependencies'
}
// Copy all dependencies to the lib directory
task install(type: Copy) {
dependsOn build
from configurations.runtime
from jar
into "${project.projectDir}/lib"
}

View File

@@ -0,0 +1,144 @@
/*
© 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 com.ibm.mqcontainer.test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.logging.Logger;
import javax.jms.JMSContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import com.ibm.mq.jms.MQConnectionFactory;
import com.ibm.mq.jms.MQQueue;
import com.ibm.msg.client.wmq.WMQConstants;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
class JMSTests {
private static final Logger LOGGER = Logger.getLogger(JMSTests.class.getName());
protected static final String ADDR = System.getenv("MQ_PORT_1414_TCP_ADDR");
protected static final String USER = System.getenv("MQ_USERNAME");
protected static final String PASSWORD = System.getenv("MQ_PASSWORD");
protected static final String CHANNEL = System.getenv("MQ_CHANNEL");
protected static final String TRUSTSTORE = System.getenv("MQ_TLS_TRUSTSTORE");
protected static final String PASSPHRASE = System.getenv("MQ_TLS_PASSPHRASE");
private JMSContext context;
static SSLSocketFactory createSSLSocketFactory() throws IOException, GeneralSecurityException {
KeyStore ts=KeyStore.getInstance("jks");
ts.load(new FileInputStream(TRUSTSTORE), PASSPHRASE.toCharArray());
// KeyManagerFactory kmf=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
TrustManagerFactory tmf=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
// tmf.init();
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
// Security.setProperty("crypto.policy", "unlimited");
ctx.init(null, tmf.getTrustManagers(), null);
return ctx.getSocketFactory();
}
static JMSContext create(String channel, String addr, String user, String password) throws JMSException, IOException, GeneralSecurityException {
LOGGER.info(String.format("Connecting to %s/TCP/%s(1414) as %s", channel, addr, user));
MQConnectionFactory factory = new MQConnectionFactory();
factory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
factory.setChannel(channel);
factory.setConnectionNameList(String.format("%s(1414)", addr));
// factory.setClientReconnectOptions(WMQConstants.WMQ_CLIENT_RECONNECT);
if (TRUSTSTORE == null) {
LOGGER.info("Not using TLS");
}
else {
LOGGER.info(String.format("Using TLS. Trust store=%s", TRUSTSTORE));
SSLSocketFactory ssl = createSSLSocketFactory();
factory.setSSLSocketFactory(ssl);
factory.setSSLCipherSuite("SSL_RSA_WITH_AES_128_CBC_SHA256");
// LOGGER.info(Arrays.toString(ssl.getSupportedCipherSuites()));
}
// Give up if unable to reconnect for 10 minutes
// factory.setClientReconnectTimeout(600);
// LOGGER.info(String.format("user=%s pw=%s", user, password));
return factory.createContext(user, password);
}
@BeforeAll
private static void waitForQueueManager() {
for (int i = 0; i < 20; i++) {
try {
Socket s = new Socket(ADDR, 1414);
s.close();
return;
} catch (IOException e) {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
}
}
}
}
@BeforeEach
void connect() throws Exception {
context = create(CHANNEL, ADDR, USER, PASSWORD);
}
@Test
void succeedingTest(TestInfo t) throws JMSException {
Queue queue = new MQQueue("DEV.QUEUE.1");
context.createProducer().send(queue, t.getDisplayName());
Message m = context.createConsumer(queue).receive();
assertNotNull(m.getBody(String.class));
}
// @Test
// void failingTest() {
// fail("a failing test");
// }
@Test
@Disabled("for demonstration purposes")
void skippedTest() {
// not executed
}
@AfterEach
void tearDown() {
if (context != null) {
context.close();
}
}
@AfterAll
static void tearDownAll() {
}
}

BIN
test/tls/client-trust.jks Normal file

Binary file not shown.

41
test/tls/generate-test-cert.sh Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# -*- mode: sh -*-
# © Copyright IBM Corporation 2018
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
KEY=server.key
CERT=server.crt
PKCS=server.p12
PASSWORD=passw0rd
# Create a private key and certificate in PEM format, for the server to use
openssl req \
-newkey rsa:2048 -nodes -keyout ${KEY} \
-subj "/CN=localhost" \
-x509 -days 365 -out ${CERT}
# Add the key and certificate to a PKCS #12 key store, for the server to use
openssl pkcs12 \
-inkey ${KEY} \
-in ${CERT} \
-export -out ${PKCS} \
-password pass:${PASSWORD}
# Add the certificate to a trust store in JKS format, for Java clients to use when connecting
keytool -import \
-alias server-cert \
-file ${CERT} \
-keystore client-trust.jks \
-storepass ${PASSWORD} \
-noprompt

17
test/tls/server.crt Normal file
View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICpDCCAYwCCQDft9xlN4fNFTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
b2NhbGhvc3QwHhcNMTgwMzIwMTUxODMwWhcNMTkwMzIwMTUxODMwWjAUMRIwEAYD
VQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDk
XzX0xQIZzKVX8/lDQh5lSHr5U9cBL+kURA3fEgl3ks9KjZPggfxWl4Y5dekChW/s
iknVssoNw9vI1W25qtQ81zRFQbHbpej0lLdYsS8/yZCuAVjMTp6Q9IswTwhVA6OD
5orag5dH3XQH+GsnmGXRCY7Gs93onAe3i3ShX9qpUFOJXyxCX+pLAC6kWQ3f/HI8
dujVXKsg1vHgOgGqQGwnh8gm5OeWUeuTMdD2v7Hn1OxilgNMbcewA7bpvipgm2xt
ZD0PKFDmtQ4comr25Oo+eUf1N7jSpRPOWJNxoyS9/coQUPp1Gpbk7khYHjGn7f5a
EZqQ4Hmwwh50uT+vKVxDAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAHaywC7ZLOi
3PKlidj6PWe33dEVsDL6RRb3cOqR86Ld2aD91oLrpELRhz4v2mt/GfQMIg7rc6z7
26SuPzV/7zZAv1N/vGoIFyvBXWLYP5qCwUrmykcH/wfFM80S6FJxz5Wy5MA5UzTB
HdpiQCPu4U0IKgATLDraz0xlQ61Rog56YhgJI8ulHuav5iYxqV2mwU09Hs0kXPJ7
g0PLRaSyidsXafxBKukeM9QHl8z8HN8er23oqecYo59b/Bt0c6jSrJCK39EUcoLP
HxR+Ma1SPhVKGqa3lPmaoAzsFTqaJ6fsIcbp+oEFAq0LPeqMPK7u3ygT4iTblAl8
q3isCz4Ytx4=
-----END CERTIFICATE-----

28
test/tls/server.key Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDkXzX0xQIZzKVX
8/lDQh5lSHr5U9cBL+kURA3fEgl3ks9KjZPggfxWl4Y5dekChW/siknVssoNw9vI
1W25qtQ81zRFQbHbpej0lLdYsS8/yZCuAVjMTp6Q9IswTwhVA6OD5orag5dH3XQH
+GsnmGXRCY7Gs93onAe3i3ShX9qpUFOJXyxCX+pLAC6kWQ3f/HI8dujVXKsg1vHg
OgGqQGwnh8gm5OeWUeuTMdD2v7Hn1OxilgNMbcewA7bpvipgm2xtZD0PKFDmtQ4c
omr25Oo+eUf1N7jSpRPOWJNxoyS9/coQUPp1Gpbk7khYHjGn7f5aEZqQ4Hmwwh50
uT+vKVxDAgMBAAECggEBAL91kybChCBdEcHLKQ7aP+FqAq9FOtwj7qSu6XI7DPTS
gDdgurleQM/X+Q/zaoZSmKMWzQ/79KnVqk2VoYgnUAgx5ACsMxCS59slUxFoetRf
iIxZVLj0sLuWSZsWp0We51eN0Juh9xKo9r435p4rhjDacnjkEwcQyOd4Yy9nzUpk
GDD5Vu1J9bOOKUQZ0qgjPyl/xWiwD1yfGJ0nHpQ5ucfrCO9p+n7SYsx01WcAkC8J
WP9XSXgi5uIefTWb/4m2b32jzjIgzAHkNx6yktRTjBJ7QILnKq1P8JjkNA/Awj4P
OxAz9hHHnVRuq4ZlEqfvo9p9YAbN2IH5TnmN3rGCXwECgYEA9JitVIeXCS0qIMFA
dKCmm9CT7JXccdpVllwaaYCNTb+G2RBrJqAvQEetoYJodWTIm1mNwSEORFFw0W+N
eaMzibJoJ+MZHRhiulDJaY0vwAKHkSJjDPJrPLgGMCUOLiWSAAnR4z35WfeY0e//
JbdZZemrJRyzy3o6rkRN9TQcUMUCgYEA7wTj5w5GZ8NQ7Nn8nIS2ayk+woIMHS+g
RVFufJoBeopsNJfNzGak0s+nz5q0nMGMzQsxXkbmAOLMTU3woQ7cEGjkLAfoch23
ACOe7M4rZbIk6kVNOlFESWdVdWViVd/B2a7oBqOIykoqX6VSqqrw+xghAUmd/2W1
uxjg9v01OWcCgYApE5LYRUUKF3mhspKeg3Q3apnM+4Xf4OjKrYEKArq4OdftkCJO
hEwrIV55Zysfu+Mso6d4rZJ1yq+FnJRHvy6ii0GOoUbQag36eCK7BSjluAcISpwT
yopT0hvH7hEpksmoE/4ZiYjcoQYbC5DvxpDO2qURQHa5TzeXmIT3Dt9KeQKBgQC6
UKeOXrRHAhs85ZdiMpk340jGujTTM2LNZfKoMixg5zH9tS9427IzmicHT2LmpoEo
/EaZZM65dhEnWU/vW/Py3rCuGeP5wGv8Mcgac4OknD7mVusiQGLojSIyhrsmkWs8
UnkPY76nYTSypd5Qpzt9n4tqw4XjpdcJZxVFso8glQKBgQCHlb15As73En/Q2AxL
5FY1Q1lLuO8y33ZZIRK4eynOKkbiuAh7X+ONZ4T9NtTm2J7mnltvTHZ7yeOI+VLS
LrTTBwnnNfdpp8UVPQlwzeizoDqSbr1sjFYvKOfdDDfxuzieT/4tfW9VTAxn4uOg
qpg7aRMUYUuLAH+S5atdOqXB+g==
-----END PRIVATE KEY-----

BIN
test/tls/server.p12 Normal file

Binary file not shown.