Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Dockerfile-agent
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
#

ARG UBI_BASE_IMAGE=registry.access.redhat.com/ubi9/ubi-minimal
ARG UBI_BASE_TAG=9.6-1749489516
ARG UBI_BASE_TAG=9.6-1758184547

ARG GO_TOOLSET_IMAGE=registry.access.redhat.com/ubi9/go-toolset
ARG GO_TOOLSET_TAG=1.23.9-1749636489
ARG GO_TOOLCHAIN_VERSION=go1.23.9+auto
ARG GO_TOOLSET_TAG=1.24.6-1758501173
ARG GO_TOOLCHAIN_VERSION=go1.24.6+auto

ARG GO_WORKDIR=/opt/app-root/src/go/src/github.com/ibm-messaging/mq-container-mft
ARG JDK_BASE_IMAGE=registry.access.redhat.com/ubi8/openjdk-8
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,25 @@ This image allows you to run IBM MQ Managed File Transfer Agent in a container.

See [here](archive/README.md) for an earlier implementation of MFT on cloud.

## What is new in IBM MQ MFT Agent Container 9.4.3.0?
## What is new in IBM MQ MFT Agent Container 9.4.4.0?

This version of the container image has the following updates:

- Built using the IBM MQ Managed File Transfer 9.4.3.0 LTS Redistributable binaries.
- Built using the IBM MQ Managed File Transfer 9.4.4.0 LTS Redistributable binaries.
- Container image is built using ubi9 minimal RedHat Linux image as the base image.
- Fixes issues found in internal testing and by customers.


**Earlier versions of container images**

## What is new in IBM MQ MFT Agent Container 9.4.3.0?

This version of the container image has the following updates:

- Built using the IBM MQ Managed File Transfer 9.4.3.0 LTS Redistributable binaries.
- Container image is built using ubi9 minimal RedHat Linux image as the base image.
- Fixes issues found in internal testing and by customers.

## What is new in IBM MQ MFT Agent Container 9.4.2.0?

This version of the container image has the following updates:
Expand Down Expand Up @@ -140,4 +148,4 @@ Note: The IBM MQ Advanced for Developers license does not permit further distrib

## Copyright

© Copyright IBM Corporation 2020, 2024
© Copyright IBM Corporation 2020, 2025
2 changes: 1 addition & 1 deletion cmd/runagent/agentconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ func ValidateAgentAttributes(jsonData string) error {
// Update agent.properties file with any additional properties specified in
// configuration JSON file.
func updateAgentProperties(propertiesFile string, agentConfig string, sectionName string, bridgeAgent bool) bool {
f, err := os.OpenFile(propertiesFile, os.O_APPEND|os.O_WRONLY, 0644)
f, err := os.OpenFile(propertiesFile, os.O_APPEND|os.O_WRONLY, 0600) //Git 106 fix: Restricting file permission
if err != nil {
utils.PrintLog(fmt.Sprintf(utils.MFT_CONT_ERR_OPN_FILE_0067, propertiesFile, err))
return false
Expand Down
2 changes: 1 addition & 1 deletion cmd/runagent/agentconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

"github.com/ibm-messaging/mq-container-mft/pkg/utils"
)

//
/*
* Unit test program to test methods of runagent.
*/
Expand Down
25 changes: 21 additions & 4 deletions cmd/runagent/configutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ import (
"github.com/tidwall/gjson"
)



// Update coordination and command properties file with any additional properties specified in
// configuration JSON file.
func UpdateProperties(propertiesFile string, agentConfig string, sectionName string) error {
f, err := os.OpenFile(propertiesFile, os.O_APPEND|os.O_WRONLY, 0644)
f, err := os.OpenFile(propertiesFile, os.O_APPEND|os.O_WRONLY, 0600) //Git 106 fix: Restricting file permission
if err != nil {
errorMsg := fmt.Sprintf(utils.MFT_CONT_ERR_OPN_FILE_0067, propertiesFile, err)
return errors.New(errorMsg)
Expand Down Expand Up @@ -95,7 +97,7 @@ func createUserSandbox(sandboxXmlFileName string) error {
var errCusbox error = nil

// Open existing UserSandboxes.xml file
userSandBoxXmlFile, err := os.OpenFile(sandboxXmlFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
userSandBoxXmlFile, err := os.OpenFile(sandboxXmlFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) //Git 106 fix: Restricting file permission
// if we os.Open returns an error then handle it
if err != nil {
errorMsg := fmt.Sprintf(utils.MFT_CONT_ERR_OPN_SNDBOX_FILE_0065, sandboxXmlFileName, err)
Expand Down Expand Up @@ -176,7 +178,7 @@ func createUserSandbox(sandboxXmlFileName string) error {
*/
func setupCredentials(mqmftCredentialsXmlFileName string, bufferCred string) error {
// Create an empty credentials file, truncate if one exists
mqmftCredentialsXmlFile, err := os.OpenFile(mqmftCredentialsXmlFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
mqmftCredentialsXmlFile, err := os.OpenFile(mqmftCredentialsXmlFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) //Git 106 fix: Restricting file permission
// if we os.Open returns an error then handle it
if err != nil {
errorMsg := fmt.Sprintf(utils.MFT_CONT_ERR_OPN_CRED_FILE_0064, mqmftCredentialsXmlFileName, err)
Expand All @@ -198,6 +200,7 @@ func setupCredentials(mqmftCredentialsXmlFileName string, bufferCred string) err
return nil
}


/**
* Update XML data with credentials of queue manager
*/
Expand Down Expand Up @@ -251,9 +254,23 @@ func UpdateXmlWithQmgrCredentials(xmlWriter *xmldom.Document, configData string,
utils.PrintLog(fmt.Sprintf(utils.MFT_CONT_CRED_NOT_AVAIL_0061, qmName))
}
}

//Git 106 fix: Scrubbing sensitive data
zeroString(&mqPassword)
zeroString(&plainTextPassword)

return errReturn
}

//Git 106 fix: Function to scrub sensitive data
func zeroString(s *string) {
b := []byte(*s)
for i := range b {
b[i] = 0
}
*s = ""
}

/**
* Update XML data with credentials of key store/trust store to xml file.
*/
Expand Down Expand Up @@ -340,7 +357,7 @@ func Base64Decode(encodedText string) (string, error) {

// Decoding successful.
return string(data), nil
}
}

/**
* Process connection security attributes
Expand Down
6 changes: 3 additions & 3 deletions cmd/runagent/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func logTermination(args ...interface{}) {
// Write the message to the termination log. This is not the default place
// that Kubernetes will look for termination information.
eventLog.Debugf("Writing termination message: %v", msg)
err := ioutil.WriteFile("/run/termination-log", []byte(msg), 0660)
err := ioutil.WriteFile("/run/termination-log", []byte(msg), 0600) //Git 106 fix: Restricting file permission
if err != nil {
eventLog.Debug(err)
}
Expand Down Expand Up @@ -487,7 +487,7 @@ func configureLogger(name string, logUrl string, logKey string, logType string,
if err != nil {
eventLog.Printf("Failed to process log message - %v", err)
} else {
eventLog.Printf(formatJSON(obj))
eventLog.Println(formatJSON(obj))
}
return true
}, nil
Expand All @@ -503,7 +503,7 @@ func configureLogger(name string, logUrl string, logKey string, logType string,
if err != nil {
eventLog.Printf("Failed to process log message - %v", err)
} else {
eventLog.Printf(formatBasic(obj))
eventLog.Println(formatBasic(obj))
}
return true
}, nil
Expand Down
38 changes: 0 additions & 38 deletions cmd/runagent/runagent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,41 +87,3 @@ func TestReadConfigurationDataFromFile(t *testing.T) {
}
}

// Test updating of agent properties file
func TestupdateAgentProperties(t *testing.T) {
configDataValid := "{\"dataPath\":\"/mqmft/mftdata\",\"monitoringInterval\":300,\"displayAgentLogs\":true,\"displayLineCount\":50,\"waitTimeToStart\":10,\"coordinationQMgr\":{\"name\":\"QUICKSTART\",\"host\":\"10.254.0.4\",\"port\":1414,\"channel\":\"MFT_HA_CHN\"},\"commandsQMgr\":{\"name\":\"QUICKSTART\",\"host\":\"10.254.0.4\",\"port\":1414,\"channel\":\"MFT_HA_CHN\"},\"agent\":{\"name\":\"KXAGNT\",\"type\":\"STANDARD\",\"qmgrName\":\"QUICKSTART\",\"qmgrHost\":\"10.254.0.4\",\"qmgrPort\":1414,\"qmgrChannel\":\"MFT_HA_CHN\",\"credentialsFile\":\"/usr/local/bin/MQMFTCredentials.xml\",\"protocolBridge\":{\"credentialsFile\":\"/usr/local/bin/ProtocolBridgeCredentials.xml\",\"serverType\":\"SFTP\",\"serverHost\":\"9.199.144.110\",\"serverTimezone\":\"\",\"serverPlatform\":\"UNIX\",\"serverLocale\":\"en-US\",\"serverFileEncoding\":\"UTF-8\",\"serverPort\":22,\"serverTrustStoreFile\":\"\",\"serverLimitedWrite\":\"\",\"serverListFormat\":\"\",\"serverUserId\":\"root\",\"serverPassword\":\"Kitt@n0or\"},\"additionalProperties\":{\"enableQueueInputOutput\":\"true\"}}"
initialProps := "agentQMgr=MFTQM\nagentQMgrPort=1414\nagentDesc=\nagentQMgrHost=localhost\nagentQMgrChannel=MFT_CHN\nagentName=SRC\ntrace=com.ibm.wmqfte=all"
compareTemplate := "agentQMgr=MFTQM\nagentQMgrPort=1414\nagentDesc=\nagentQMgrHost=localhost\nagentQMgrChannel=MFT_CHN\nagentName=SRC\ntrace=com.ibm.wmqfte=all\nenableQueueInputOutput=true"

agentProps, err := ioutil.TempFile("", t.Name())
if err != nil {
t.Fatal(err)
}
defer os.Remove(agentProps.Name())
t.Log(agentProps.Name())
agentPropsF, err := os.OpenFile(agentProps.Name(), os.O_WRONLY, 0644)
if err != nil {
t.Fatal(err)
}
// Write initial properties into file and close
fmt.Fprintln(agentPropsF, initialProps)
agentPropsF.Close()

// Update the agent.properties file with data from configuration file
updateAgentProperties(agentProps.Name(), configDataValid, "additionalProperties", false)

content, err := ioutil.ReadFile(agentProps.Name())
if err != nil {
t.Fatal(err)
}

// Convert []byte to string and print to screen
updatedProps := string(content)

// Now compare with template
if strings.EqualFold(updatedProps, compareTemplate) == true {
t.Log("OK: Properties file updated as expected")
} else {
t.Fatal("Properties file not updated correctly")
}
}
22 changes: 15 additions & 7 deletions cmd/runagent/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ import (
"bytes"
"errors"
"fmt"
"math/rand"
"os"
"os/exec"
"path/filepath"
"strings"
"time"

"github.com/ibm-messaging/mq-container-mft/pkg/utils"

//Git 106 fix: Using crypto/rand for secure random password generation
"crypto/rand"
"math/big"
)

/**
Expand Down Expand Up @@ -114,16 +116,22 @@ func CreateKeyStore(keyStoreDir string, keyStoreFile string, certFilePath string
}

// Generates a random 12 character password from the characters a-z, A-Z, 0-9
//Git 106 fix: Using crypto/rand for secure random password generation
func generateRandomPassword() string {
rand.Seed(time.Now().Unix())
validChars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
validcharArray := []byte(validChars)
password := ""
validCharArray := []byte(validChars)
password := make([]byte, 12)

for i := 0; i < 12; i++ {
password = password + string(validcharArray[rand.Intn(len(validcharArray))])
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(validCharArray))))
if err != nil {
// Fallback: deterministic password if crypto/rand fails
return "DefaultPass123"
}
password[i] = validCharArray[n.Int64()]
}

return password
return string(password)
}

// Search the specified directory for certificate files
Expand Down
7 changes: 3 additions & 4 deletions credentialsexit/BridgeCredentialExit/.classpath
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="/opt/mqm/mqft/lib/com.ibm.wmqfte.exitroutines.api.jar"/>
<classpathentry kind="lib" path="thirdparty/json-20240303.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="lib" path="thirdparty/com.ibm.wmqfte.exitroutines.api.jar"/>
<classpathentry kind="lib" path="thirdparty/json-20250517.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
4 changes: 2 additions & 2 deletions credentialsexit/BridgeCredentialExit/src/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
Manifest-Version: 1.0
Created-By: IBM Corporation
Specification-Title: com.ibm.bridgecredentialexit.jar
Specification-Version: 9.4.3.0
Specification-Version: 9.4.4.0
Specification-Vendor: IBM Corporation
Implementation-Title: com.ibm.bridgecredentialexit.jar
Implementation-Version: 9.4.3.0
Implementation-Version: 9.4.4.0
Implementation-Vendor: IBM Corporation
Sealed: true

Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,18 @@ public void testMapUseridExistknownV1FTPHost() {
props.put("protocolBridgeCredentialConfiguration", file.getAbsolutePath());
assertTrue(exit.initialize(props));
ProtocolServerEndPoint pse = new ProtocolServerEndPoint("10.17.68.52", "FTP", "9.122.123.124", 22);
CredentialExitResult cer = exit.mapMQUserId(pse, "shashikantht");
CredentialExitResult cer = exit.mapMQUserId(pse, "longuser1");
assertEquals(cer.getResultCode(), CredentialExitResultCode.USER_SUCCESSFULLY_MAPPED);
Credentials creds = cer.getCredentials();
System.out.println("UserId: " + creds.getUserId());
assertEquals(creds.getUserId().get(), "root");

ProtocolServerEndPoint pse1 = new ProtocolServerEndPoint("10.18.68.52", "FTP", "9.122.123.124", 22);
CredentialExitResult cer1 = exit.mapMQUserId(pse1, "shashikantht");
CredentialExitResult cer1 = exit.mapMQUserId(pse1, "longuser1");
assertEquals(cer1.getResultCode(), CredentialExitResultCode.USER_SUCCESSFULLY_MAPPED);
Credentials creds1 = cer1.getCredentials();
System.out.println("UserId: " + creds.getUserId());
assertEquals(creds1.getUserId().get(), "greekman");
assertEquals(creds1.getPassword().get(), "Santorini");

}

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/ibm-messaging/mq-container-mft

go 1.23.0
go 1.24.6

toolchain go1.23.6
toolchain go1.24.6

require (
github.com/Jeffail/gabs v1.4.0
Expand Down
11 changes: 6 additions & 5 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
© Copyright IBM Corporation 2020, 2024
© Copyright IBM Corporation 2020, 2025

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -200,16 +200,17 @@ func CreatePath(dataPath string) error {
_, err := os.Stat(dataPath)
if err != nil {
if os.IsNotExist(err) {
err := os.MkdirAll(dataPath, 0777)
err := os.MkdirAll(dataPath, 0700) //Git 106 fix: Restricting directory permission
if err != nil {
return fmt.Errorf("failed to create path %s due to error: %v", dataPath, err)
} else {
}
/** else {
// Change permissions Linux.
err = os.Chmod(dataPath, 0777)
if err != nil {
return fmt.Errorf("failed to modify permissions on path %s due to error %v", dataPath, err)
}
}
} **/
} else {
return fmt.Errorf("an error occurred while checking e %v", err)
}
Expand Down Expand Up @@ -300,7 +301,7 @@ func DoesFileExist(fileName string) bool {
// Write the given buffer to specified file
func WriteData(fileName string, bufferToWrite string) error {
// Create an empty credentials file, truncate if one exists
filePointer, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
filePointer, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) //Git 106 fix: Restricting file permission
// if we os.Open returns an error then handle it
if err != nil {
return err
Expand Down