diff --git a/Dockerfile-agent b/Dockerfile-agent index 31f25b5..e8f07af 100644 --- a/Dockerfile-agent +++ b/Dockerfile-agent @@ -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 diff --git a/README.md b/README.md index 86f37f1..3759bd2 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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 diff --git a/cmd/runagent/agentconfig.go b/cmd/runagent/agentconfig.go index c47bc1c..1013452 100644 --- a/cmd/runagent/agentconfig.go +++ b/cmd/runagent/agentconfig.go @@ -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 diff --git a/cmd/runagent/agentconfig_test.go b/cmd/runagent/agentconfig_test.go index 091fa20..83ba44b 100644 --- a/cmd/runagent/agentconfig_test.go +++ b/cmd/runagent/agentconfig_test.go @@ -24,7 +24,7 @@ import ( "github.com/ibm-messaging/mq-container-mft/pkg/utils" ) - +// /* * Unit test program to test methods of runagent. */ diff --git a/cmd/runagent/configutils.go b/cmd/runagent/configutils.go index e44cac4..43ad29a 100644 --- a/cmd/runagent/configutils.go +++ b/cmd/runagent/configutils.go @@ -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) @@ -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) @@ -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) @@ -198,6 +200,7 @@ func setupCredentials(mqmftCredentialsXmlFileName string, bufferCred string) err return nil } + /** * Update XML data with credentials of queue manager */ @@ -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. */ @@ -340,7 +357,7 @@ func Base64Decode(encodedText string) (string, error) { // Decoding successful. return string(data), nil -} +} /** * Process connection security attributes diff --git a/cmd/runagent/logging.go b/cmd/runagent/logging.go index 599f133..5b714b3 100644 --- a/cmd/runagent/logging.go +++ b/cmd/runagent/logging.go @@ -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) } @@ -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 @@ -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 diff --git a/cmd/runagent/runagent_test.go b/cmd/runagent/runagent_test.go index 261079a..fa15cdf 100644 --- a/cmd/runagent/runagent_test.go +++ b/cmd/runagent/runagent_test.go @@ -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") - } -} diff --git a/cmd/runagent/tls.go b/cmd/runagent/tls.go index 7e02b25..7db072f 100644 --- a/cmd/runagent/tls.go +++ b/cmd/runagent/tls.go @@ -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" ) /** @@ -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 diff --git a/credentialsexit/BridgeCredentialExit/.classpath b/credentialsexit/BridgeCredentialExit/.classpath index b8229a0..0ea9f8f 100644 --- a/credentialsexit/BridgeCredentialExit/.classpath +++ b/credentialsexit/BridgeCredentialExit/.classpath @@ -1,9 +1,8 @@ + - - - - + + diff --git a/credentialsexit/BridgeCredentialExit/src/MANIFEST.MF b/credentialsexit/BridgeCredentialExit/src/MANIFEST.MF index a015274..e9088ba 100644 --- a/credentialsexit/BridgeCredentialExit/src/MANIFEST.MF +++ b/credentialsexit/BridgeCredentialExit/src/MANIFEST.MF @@ -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 diff --git a/credentialsexit/BridgeCredentialExit/src/com/ibm/bridgecredentialexit/TestPBAExit.java b/credentialsexit/BridgeCredentialExit/src/com/ibm/bridgecredentialexit/TestPBAExit.java index 20f28a7..76a0344 100644 --- a/credentialsexit/BridgeCredentialExit/src/com/ibm/bridgecredentialexit/TestPBAExit.java +++ b/credentialsexit/BridgeCredentialExit/src/com/ibm/bridgecredentialexit/TestPBAExit.java @@ -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"); } diff --git a/go.mod b/go.mod index d63c30f..ccb606f 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index f31ca03..7d6115c 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -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. @@ -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) } @@ -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