TLS and HTTPS configuration in default developer config
This commit is contained in:
136
cmd/runmqdevserver/keystore.go
Normal file
136
cmd/runmqdevserver/keystore.go
Normal 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
|
||||||
|
}
|
||||||
@@ -129,6 +129,19 @@ func doMain() error {
|
|||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func updateMQSC(appPasswordRequired bool) error {
|
|||||||
} else {
|
} else {
|
||||||
checkClient = "ASQMGR"
|
checkClient = "ASQMGR"
|
||||||
}
|
}
|
||||||
const mqsc string = "/etc/mqm/dev.mqsc"
|
const mqsc string = "/etc/mqm/10-dev.mqsc"
|
||||||
if os.Getenv("MQ_DEV") == "true" {
|
if os.Getenv("MQ_DEV") == "true" {
|
||||||
const mqscTemplate string = mqsc + ".tpl"
|
const mqscTemplate string = mqsc + ".tpl"
|
||||||
// Re-configure channel if app password not set
|
// Re-configure channel if app password not set
|
||||||
|
|||||||
140
cmd/runmqdevserver/tls.go
Normal file
140
cmd/runmqdevserver/tls.go
Normal 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
|
||||||
|
}
|
||||||
@@ -17,7 +17,10 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
// postInit is run after /var/mqm is set up
|
// postInit is run after /var/mqm is set up
|
||||||
// This version of postInit is only included as part of the MQ Advanced for Developers build
|
// 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()
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -41,6 +42,26 @@ func startWebServer() error {
|
|||||||
return nil
|
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 {
|
func configureWebServer() error {
|
||||||
_, err := os.Stat("/opt/mqm/bin/strmqweb")
|
_, err := os.Stat("/opt/mqm/bin/strmqweb")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -76,7 +97,6 @@ func configureWebServer() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
if !exists {
|
if !exists {
|
||||||
err := os.MkdirAll(to, 0770)
|
err := os.MkdirAll(to, 0770)
|
||||||
@@ -84,7 +104,6 @@ func configureWebServer() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("Directory: %v --> %v", from, to)
|
|
||||||
} else {
|
} else {
|
||||||
if exists {
|
if exists {
|
||||||
err := os.Remove(to)
|
err := os.Remove(to)
|
||||||
@@ -92,13 +111,11 @@ func configureWebServer() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: Permissions. Can't rely on them being set in Dockerfile
|
err := CopyFile(from, to)
|
||||||
err := os.Link(from, to)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug(err)
|
log.Debug(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("File: %v", from)
|
|
||||||
}
|
}
|
||||||
err = os.Chown(to, uid, gid)
|
err = os.Chown(to, uid, gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -32,9 +32,10 @@ DEFINE AUTHINFO('DEV.AUTHINFO') AUTHTYPE(IDPWOS) CHCKCLNT(REQDADM) CHCKLOCL(OPTI
|
|||||||
ALTER QMGR CONNAUTH('DEV.AUTHINFO')
|
ALTER QMGR CONNAUTH('DEV.AUTHINFO')
|
||||||
REFRESH SECURITY(*) TYPE(CONNAUTH)
|
REFRESH SECURITY(*) TYPE(CONNAUTH)
|
||||||
|
|
||||||
|
* Developer channels (Application + Admin)
|
||||||
* Developer channels (Application + Admin)
|
* Developer channels (Application + Admin)
|
||||||
DEFINE CHANNEL('DEV.ADMIN.SVRCONN') CHLTYPE(SVRCONN) REPLACE
|
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
|
* Developer channel authentication rules
|
||||||
SET CHLAUTH('*') TYPE(ADDRESSMAP) ADDRESS('*') USERSRC(NOACCESS) DESCR('Back-stop rule - Blocks everyone') ACTION(REPLACE)
|
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)
|
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
|
* Developer authority records
|
||||||
SET AUTHREC PROFILE('DEV.AUTHINFO') GROUP('root') OBJTYPE(AUTHINFO) AUTHADD(CHG,DLT,DSP,INQ)
|
SET AUTHREC PROFILE('self') GROUP('mqclient') OBJTYPE(QMGR) AUTHADD(CONNECT,INQ)
|
||||||
SET AUTHREC PROFILE('DEV.AUTHINFO') GROUP('mqm') OBJTYPE(AUTHINFO) AUTHADD(CHG,DLT,DSP,INQ)
|
|
||||||
SET AUTHREC PROFILE('DEV.**') GROUP('mqclient') OBJTYPE(QUEUE) AUTHADD(BROWSE,GET,INQ,PUT)
|
SET AUTHREC PROFILE('DEV.**') GROUP('mqclient') OBJTYPE(QUEUE) AUTHADD(BROWSE,GET,INQ,PUT)
|
||||||
SET AUTHREC PROFILE('DEV.**') GROUP('mqclient') OBJTYPE(TOPIC) AUTHADD(PUB,SUB)
|
SET AUTHREC PROFILE('DEV.**') GROUP('mqclient') OBJTYPE(TOPIC) AUTHADD(PUB,SUB)
|
||||||
|
|
||||||
@@ -40,13 +40,17 @@ ENV MQ_ADMIN_PASSWORD=passw0rd
|
|||||||
## 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 \
|
||||||
&& useradd app -G mqclient,mqm \
|
&& useradd app -G mqclient \
|
||||||
&& echo admin:$MQ_ADMIN_PASSWORD | chpasswd
|
&& 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/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 MQSC for default developer configuration
|
# 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 web XML files for default developer configuration
|
||||||
COPY incubating/mqadvanced-server-dev/web /etc/mqm/web
|
COPY incubating/mqadvanced-server-dev/web /etc/mqm/web
|
||||||
RUN chmod +x /usr/local/bin/runmq*
|
RUN chmod +x /usr/local/bin/runmq*
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<keyStore id="MQWebKeyStore" location="/var/mqm/web/installations/Installation1/servers/mqweb/key.jks" type="JKS" password="${env.MQ_TLS_PASSPHRASE}"/>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<keyStore id="MQWebTrustStore" location="/var/mqm/web/installations/Installation1/servers/mqweb/trust.jks" type="JKS" password="${env.MQ_TLS_PASSPHRASE}"/>
|
<server>
|
||||||
<ssl id="thisSSLConfig" clientAuthenticationSupported="true" keyStoreRef="MQWebKeyStore" trustStoreRef="MQWebTrustStore" sslProtocol="TLSv1.2" serverKeyAlias="webcert"/>
|
<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"/>
|
<sslDefault sslRef="thisSSLConfig"/>
|
||||||
|
</server>
|
||||||
@@ -1 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<server>
|
||||||
<sslDefault sslRef="mqDefaultSSLConfig"/>
|
<sslDefault sslRef="mqDefaultSSLConfig"/>
|
||||||
|
</server>
|
||||||
@@ -18,14 +18,15 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"path/filepath"
|
||||||
"net/http"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/go-connections/nat"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDevGoldenPath(t *testing.T) {
|
func TestDevGoldenPath(t *testing.T) {
|
||||||
@@ -35,7 +36,12 @@ func TestDevGoldenPath(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
containerConfig := container.Config{
|
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)
|
id := runContainer(t, cli, &containerConfig)
|
||||||
|
|
||||||
@@ -43,30 +49,74 @@ func TestDevGoldenPath(t *testing.T) {
|
|||||||
waitForReady(t, cli, id)
|
waitForReady(t, cli, id)
|
||||||
waitForWebReady(t, cli, id)
|
waitForWebReady(t, cli, id)
|
||||||
|
|
||||||
timeout := time.Duration(30 * time.Second)
|
t.Run("REST", func(t *testing.T) {
|
||||||
// Disable TLS verification (server uses a self-signed certificate by default,
|
// Disable TLS verification (server uses a self-signed certificate by default,
|
||||||
// so verification isn't useful anyway)
|
// so verification isn't useful anyway)
|
||||||
tr := &http.Transport{
|
testREST(t, cli, id, &tls.Config{
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
},
|
})
|
||||||
}
|
})
|
||||||
httpClient := http.Client{
|
t.Run("JMS", func(t *testing.T) {
|
||||||
Timeout: timeout,
|
runJMSTests(t, cli, id, false)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the container cleanly
|
// Stop the container cleanly
|
||||||
stopContainer(t, cli, id)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,14 +18,26 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const devAdminPassword string = "passw0rd"
|
||||||
|
const devAppPassword string = "passw0rd"
|
||||||
|
|
||||||
func waitForWebReady(t *testing.T, cli *client.Client, ID string) {
|
func waitForWebReady(t *testing.T, cli *client.Client, ID string) {
|
||||||
config := tls.Config{InsecureSkipVerify: true}
|
config := tls.Config{InsecureSkipVerify: true}
|
||||||
a := fmt.Sprintf("localhost:%s", getWebPort(t, cli, ID))
|
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 {
|
if err == nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
// Extra sleep to allow web apps to start
|
// 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")
|
t.Log("MQ web server is ready")
|
||||||
return
|
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
6
test/messaging/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.classpath
|
||||||
|
.gradle
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
bin
|
||||||
|
|
||||||
38
test/messaging/Dockerfile
Normal file
38
test/messaging/Dockerfile
Normal 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"]
|
||||||
39
test/messaging/build.gradle
Normal file
39
test/messaging/build.gradle
Normal 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"
|
||||||
|
}
|
||||||
@@ -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
BIN
test/tls/client-trust.jks
Normal file
Binary file not shown.
41
test/tls/generate-test-cert.sh
Executable file
41
test/tls/generate-test-cert.sh
Executable 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
17
test/tls/server.crt
Normal 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
28
test/tls/server.key
Normal 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
BIN
test/tls/server.p12
Normal file
Binary file not shown.
Reference in New Issue
Block a user