Cherry-pick MM-66789: Restrict log downloads to a root path for support packets (#35164)
Automatic Merge
Этот коммит содержится в:
@@ -48,6 +48,9 @@ func TestGetMattermostLog(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Set MM_LOG_PATH to allow log file reads from our temp directory
|
||||
t.Setenv("MM_LOG_PATH", dir)
|
||||
|
||||
// Enable log file but point to an empty directory to get an error trying to read the file
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.LogSettings.EnableFile = true
|
||||
@@ -71,6 +74,33 @@ func TestGetMattermostLog(t *testing.T) {
|
||||
require.NotNil(t, fileData)
|
||||
assert.Equal(t, "mattermost.log", fileData.Filename)
|
||||
assert.Positive(t, len(fileData.Body))
|
||||
|
||||
// Test path validation: FileLocation outside MM_LOG_PATH should be blocked
|
||||
t.Run("path validation prevents reading files outside log directory", func(t *testing.T) {
|
||||
// Create a directory outside the allowed log root
|
||||
outsideDir, err := os.MkdirTemp("", "outside")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(outsideDir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Create a file that would be read if validation fails
|
||||
outsideLogLocation := config.GetLogFileLocation(outsideDir)
|
||||
err = os.WriteFile(outsideLogLocation, []byte("secret data"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set FileLocation to the outside directory (MM_LOG_PATH is still set to 'dir')
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.LogSettings.FileLocation = outsideDir
|
||||
})
|
||||
|
||||
// Should be blocked by path validation
|
||||
fileData, err = th.Service.GetLogFile(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "outside allowed logging directory")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetNotificationLogFile(t *testing.T) {
|
||||
@@ -91,10 +121,20 @@ func TestGetNotificationLogFile(t *testing.T) {
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
// Disable file target before cleaning up to avoid a race between
|
||||
// removing the directory and the file getting written again.
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.NotificationLogSettings.EnableFile = false
|
||||
})
|
||||
th.Service.NotificationsLogger().Flush()
|
||||
|
||||
err = os.RemoveAll(dir)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// Set MM_LOG_PATH to allow log file reads from our temp directory
|
||||
t.Setenv("MM_LOG_PATH", dir)
|
||||
|
||||
// Enable notifications file but point to an empty directory to get an error trying to read the file
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.NotificationLogSettings.EnableFile = true
|
||||
@@ -118,6 +158,33 @@ func TestGetNotificationLogFile(t *testing.T) {
|
||||
require.NotNil(t, fileData)
|
||||
assert.Equal(t, "notifications.log", fileData.Filename)
|
||||
assert.Positive(t, len(fileData.Body))
|
||||
|
||||
// Test path validation: FileLocation outside MM_LOG_PATH should be blocked
|
||||
t.Run("path validation prevents reading files outside log directory", func(t *testing.T) {
|
||||
// Create a directory outside the allowed log root
|
||||
outsideDir, err := os.MkdirTemp("", "outside")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(outsideDir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Create a file that would be read if validation fails
|
||||
outsideLogLocation := config.GetNotificationsLogFileLocation(outsideDir)
|
||||
err = os.WriteFile(outsideLogLocation, []byte("secret data"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set FileLocation to the outside directory (MM_LOG_PATH is still set to 'dir')
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.NotificationLogSettings.FileLocation = outsideDir
|
||||
})
|
||||
|
||||
// Should be blocked by path validation
|
||||
fileData, err = th.Service.GetNotificationLogFile(th.Context)
|
||||
assert.Nil(t, fileData)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "outside allowed logging directory")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAdvancedLogs(t *testing.T) {
|
||||
@@ -134,6 +201,9 @@ func TestGetAdvancedLogs(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Set MM_LOG_PATH to allow advanced logging to write to our temp directory
|
||||
t.Setenv("MM_LOG_PATH", dir)
|
||||
|
||||
// Setup log files for each setting
|
||||
optLDAP := map[string]string{
|
||||
"filename": path.Join(dir, "ldap.log"),
|
||||
@@ -243,9 +313,10 @@ func TestGetAdvancedLogs(t *testing.T) {
|
||||
require.NotNil(t, notifFile)
|
||||
testlib.AssertLog(t, bytes.NewBuffer(notifFile.Body), mlog.LvlInfo.Name, "Some Notification")
|
||||
})
|
||||
// Disable AdvancedLoggingJSON
|
||||
// Disable AdvancedLoggingJSON for all log settings
|
||||
th.Service.UpdateConfig(func(c *model.Config) {
|
||||
c.LogSettings.AdvancedLoggingJSON = nil
|
||||
c.NotificationLogSettings.AdvancedLoggingJSON = nil
|
||||
})
|
||||
t.Run("No logs returned when AdvancedLoggingJSON is empty", func(t *testing.T) {
|
||||
// Confirm no logs get returned
|
||||
@@ -253,4 +324,119 @@ func TestGetAdvancedLogs(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fileDatas, 0)
|
||||
})
|
||||
|
||||
t.Run("path validation prevents reading files outside log directory", func(t *testing.T) {
|
||||
// Create a temporary directory to use as the log root
|
||||
logDir, err := os.MkdirTemp("", "logs")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(logDir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Set MM_LOG_PATH to restrict log file access to logDir
|
||||
t.Setenv("MM_LOG_PATH", logDir)
|
||||
|
||||
// Create a file outside the log directory that should not be accessible
|
||||
outsideDir, err := os.MkdirTemp("", "outside")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = os.RemoveAll(outsideDir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
secretFile := path.Join(outsideDir, "secret.txt")
|
||||
err = os.WriteFile(secretFile, []byte("secret data"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a valid log file inside the log directory
|
||||
validLog := path.Join(logDir, "valid.log")
|
||||
err = os.WriteFile(validLog, []byte("valid log data"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test 1: Attempt to read file outside log directory using absolute path
|
||||
optOutside := map[string]string{
|
||||
"filename": secretFile,
|
||||
}
|
||||
dataOutside, err := json.Marshal(optOutside)
|
||||
require.NoError(t, err)
|
||||
|
||||
logCfgOutside := mlog.LoggerConfiguration{
|
||||
"malicious": mlog.TargetCfg{
|
||||
Type: "file",
|
||||
Format: "json",
|
||||
Levels: []mlog.Level{mlog.LvlError},
|
||||
Options: dataOutside,
|
||||
},
|
||||
}
|
||||
logCfgDataOutside, err := json.Marshal(logCfgOutside)
|
||||
require.NoError(t, err)
|
||||
|
||||
th.Service.UpdateConfig(func(c *model.Config) {
|
||||
c.LogSettings.AdvancedLoggingJSON = logCfgDataOutside
|
||||
})
|
||||
|
||||
fileDatas, err := th.Service.GetAdvancedLogs(th.Context)
|
||||
// Should return error indicating path is outside allowed directory
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "outside allowed logging directory")
|
||||
require.Len(t, fileDatas, 0)
|
||||
|
||||
// Test 2: Attempt path traversal attack
|
||||
traversalPath := path.Join(logDir, "..", "..", "etc", "passwd")
|
||||
optTraversal := map[string]string{
|
||||
"filename": traversalPath,
|
||||
}
|
||||
dataTraversal, err := json.Marshal(optTraversal)
|
||||
require.NoError(t, err)
|
||||
|
||||
logCfgTraversal := mlog.LoggerConfiguration{
|
||||
"traversal": mlog.TargetCfg{
|
||||
Type: "file",
|
||||
Format: "json",
|
||||
Levels: []mlog.Level{mlog.LvlError},
|
||||
Options: dataTraversal,
|
||||
},
|
||||
}
|
||||
logCfgDataTraversal, err := json.Marshal(logCfgTraversal)
|
||||
require.NoError(t, err)
|
||||
|
||||
th.Service.UpdateConfig(func(c *model.Config) {
|
||||
c.LogSettings.AdvancedLoggingJSON = logCfgDataTraversal
|
||||
})
|
||||
|
||||
fileDatas, err = th.Service.GetAdvancedLogs(th.Context)
|
||||
// Should return error for path traversal attempt
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "outside")
|
||||
require.Len(t, fileDatas, 0)
|
||||
|
||||
// Test 3: Valid path within log directory should work
|
||||
optValid := map[string]string{
|
||||
"filename": validLog,
|
||||
}
|
||||
dataValid, err := json.Marshal(optValid)
|
||||
require.NoError(t, err)
|
||||
|
||||
logCfgValid := mlog.LoggerConfiguration{
|
||||
"valid": mlog.TargetCfg{
|
||||
Type: "file",
|
||||
Format: "json",
|
||||
Levels: []mlog.Level{mlog.LvlError},
|
||||
Options: dataValid,
|
||||
},
|
||||
}
|
||||
logCfgDataValid, err := json.Marshal(logCfgValid)
|
||||
require.NoError(t, err)
|
||||
|
||||
th.Service.UpdateConfig(func(c *model.Config) {
|
||||
c.LogSettings.AdvancedLoggingJSON = logCfgDataValid
|
||||
})
|
||||
|
||||
fileDatas, err = th.Service.GetAdvancedLogs(th.Context)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fileDatas, 1)
|
||||
require.Equal(t, "valid.log", fileDatas[0].Filename)
|
||||
require.Equal(t, []byte("valid log data"), fileDatas[0].Body)
|
||||
})
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user