Cherry-pick MM-66789: Restrict log downloads to a root path for support packets (#35164)
Some checks are pending
ESR Upgrade / Run ESR upgrade script from 5.37 to 7.8 (push) Waiting to run
ESR Upgrade / Run ESR upgrade script from 5.37 to 6.3 (push) Waiting to run
ESR Upgrade / Run ESR upgrade script from 6.3 to 7.8 (push) Waiting to run
Server CI Master / master-ci (push) Waiting to run
Web App CI Master / master-ci (push) Waiting to run

Automatic Merge
This commit is contained in:
Doug Lauder 2026-02-02 14:23:28 -05:00 committed by GitHub
parent d2594e5046
commit 463f7a0511
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1050 additions and 7 deletions

View file

@ -106,7 +106,14 @@ func setupTestHelper(tb testing.TB, dbStore store.Store, sqlSettings *model.SqlS
consoleLevel = mlog.LvlStdLog.Name
}
*memoryConfig.LogSettings.ConsoleLevel = consoleLevel
*memoryConfig.LogSettings.FileLocation = filepath.Join(tempWorkspace, "logs", "mattermost.log")
// Use a subdirectory within the logging root (from MM_LOG_PATH or default)
// to ensure the path is within the allowed logging root for security validation.
// Each test gets its own subdirectory based on the tempWorkspace name for isolation.
testLogsDir := filepath.Join(config.GetLogRootPath(), filepath.Base(tempWorkspace))
err = os.MkdirAll(testLogsDir, 0700)
require.NoError(tb, err, "failed to create test logs directory")
*memoryConfig.LogSettings.FileLocation = testLogsDir
*memoryConfig.NotificationLogSettings.FileLocation = testLogsDir
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
*memoryConfig.PluginSettings.AutomaticPrepackagedPlugins = false

View file

@ -88,6 +88,9 @@ func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
auditRec := c.MakeAuditRecord(model.AuditEventGenerateSupportPacket, model.AuditStatusFail)
defer c.LogAuditRec(auditRec)
// We support the existing API hence the logs are always included
// if nothing specified.
includeLogs := true
@ -99,6 +102,9 @@ func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
PluginPackets: r.Form["plugin_packets"],
}
auditRec.AddMeta("include_logs", supportPacketOptions.IncludeLogs)
auditRec.AddMeta("plugin_packets", supportPacketOptions.PluginPackets)
// Checking to see if the server has a e10 or e20 license (this feature is only permitted for servers with licenses)
if c.App.Channels().License() == nil {
c.Err = model.NewAppError("Api4.generateSupportPacket", "api.no_license", nil, "", http.StatusForbidden)
@ -132,6 +138,9 @@ func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
}
fileBytesReader := bytes.NewReader(fileBytes)
auditRec.Success()
auditRec.AddMeta("filename", outputZipFilename)
// Prevent caching so support packets are always fresh
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")

View file

@ -78,7 +78,13 @@ func setupTestHelper(dbStore store.Store, sqlStore *sqlstore.SqlStore, sqlSettin
*memoryConfig.LogSettings.ConsoleLevel = mlog.LvlStdLog.Name
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
*memoryConfig.LogSettings.FileLocation = filepath.Join(tempWorkspace, "logs", "mattermost.log")
// Use a subdirectory within the logging root (from MM_LOG_PATH or default)
// to ensure the path is within the allowed logging root for security validation.
// Each test gets its own subdirectory based on the tempWorkspace name for isolation.
testLogsDir := filepath.Join(config.GetLogRootPath(), filepath.Base(tempWorkspace))
err = os.MkdirAll(testLogsDir, 0700)
require.NoError(tb, err, "failed to create test logs directory")
*memoryConfig.LogSettings.FileLocation = testLogsDir
if updateConfig != nil {
updateConfig(memoryConfig)
}

View file

@ -107,6 +107,9 @@ func (ps *PlatformService) SaveConfig(newCfg *model.Config, sendConfigChangeClus
}
}
// Validate log file paths (logs errors for now, will block server startup in future version)
config.WarnIfLogPathsOutsideRoot(newCfg)
oldCfg, newCfg, err := ps.configStore.Set(newCfg)
if errors.Is(err, config.ErrReadOnlyConfiguration) {
return nil, nil, model.NewAppError("saveConfig", "ent.cluster.save_config.error", nil, "", http.StatusForbidden).Wrap(err)

View file

@ -215,12 +215,22 @@ func (ps *PlatformService) GetLogsSkipSend(rctx request.CTX, page, perPage int,
return lines, nil
}
func (ps *PlatformService) GetLogFile(_ request.CTX) (*model.FileData, error) {
func (ps *PlatformService) GetLogFile(rctx request.CTX) (*model.FileData, error) {
if !*ps.Config().LogSettings.EnableFile {
return nil, errors.New("Unable to retrieve mattermost logs because LogSettings.EnableFile is set to false")
}
mattermostLog := config.GetLogFileLocation(*ps.Config().LogSettings.FileLocation)
// Validate the file path to prevent arbitrary file reads
if err := ps.validateLogFilePath(mattermostLog); err != nil {
rctx.Logger().Error("Blocked attempt to read log file outside allowed root",
mlog.String("path", mattermostLog),
mlog.String("config_section", "LogSettings.FileLocation"),
mlog.Err(err))
return nil, errors.Wrapf(err, "log file path %s is outside allowed logging directory", mattermostLog)
}
mattermostLogFileData, err := os.ReadFile(mattermostLog)
if err != nil {
return nil, errors.Wrapf(err, "failed read mattermost log file at path %s", mattermostLog)
@ -232,12 +242,22 @@ func (ps *PlatformService) GetLogFile(_ request.CTX) (*model.FileData, error) {
}, nil
}
func (ps *PlatformService) GetNotificationLogFile(_ request.CTX) (*model.FileData, error) {
func (ps *PlatformService) GetNotificationLogFile(rctx request.CTX) (*model.FileData, error) {
if !*ps.Config().NotificationLogSettings.EnableFile {
return nil, errors.New("Unable to retrieve notifications logs because NotificationLogSettings.EnableFile is set to false")
}
notificationsLog := config.GetNotificationsLogFileLocation(*ps.Config().NotificationLogSettings.FileLocation)
// Validate the file path to prevent arbitrary file reads
if err := ps.validateLogFilePath(notificationsLog); err != nil {
rctx.Logger().Error("Blocked attempt to read log file outside allowed root",
mlog.String("path", notificationsLog),
mlog.String("config_section", "NotificationLogSettings.FileLocation"),
mlog.Err(err))
return nil, errors.Wrapf(err, "log file path %s is outside allowed logging directory", notificationsLog)
}
notificationsLogFileData, err := os.ReadFile(notificationsLog)
if err != nil {
return nil, errors.Wrapf(err, "failed read notifcation log file at path %s", notificationsLog)
@ -249,12 +269,26 @@ func (ps *PlatformService) GetNotificationLogFile(_ request.CTX) (*model.FileDat
}, nil
}
func (ps *PlatformService) GetAdvancedLogs(_ request.CTX) ([]*model.FileData, error) {
// validateLogFilePath validates that a log file path is within the logging root directory.
// This prevents arbitrary file read/write vulnerabilities in logging configuration.
// The logging root is determined by MM_LOG_PATH environment variable or the default logs directory.
// Currently used to validate paths when reading logs via GetAdvancedLogs.
// In future versions, this will also be used to validate paths when saving logging config.
func (ps *PlatformService) validateLogFilePath(filePath string) error {
// Get the logging root path (from env var or default logs directory)
loggingRoot := config.GetLogRootPath()
return config.ValidateLogFilePath(filePath, loggingRoot)
}
func (ps *PlatformService) GetAdvancedLogs(rctx request.CTX) ([]*model.FileData, error) {
var (
rErr *multierror.Error
ret []*model.FileData
)
rctx.Logger().Debug("Advanced logs access requested")
for name, loggingJSON := range map[string]json.RawMessage{
"LogSettings.AdvancedLoggingJSON": ps.Config().LogSettings.AdvancedLoggingJSON,
"NotificationLogSettings.AdvancedLoggingJSON": ps.Config().NotificationLogSettings.AdvancedLoggingJSON,
@ -282,6 +316,18 @@ func (ps *PlatformService) GetAdvancedLogs(_ request.CTX) ([]*model.FileData, er
rErr = multierror.Append(rErr, errors.Wrapf(err, "error decoding file target options in %s", name))
continue
}
// Validate the file path to prevent arbitrary file reads
if err := ps.validateLogFilePath(fileOption.Filename); err != nil {
rctx.Logger().Error("Blocked attempt to read log file outside allowed root",
mlog.String("path", fileOption.Filename),
mlog.String("config_section", name),
mlog.String("user_id", rctx.Session().UserId),
mlog.Err(err))
rErr = multierror.Append(rErr, errors.Wrapf(err, "log file path %s in %s is outside allowed logging directory", fileOption.Filename, name))
continue
}
data, err := os.ReadFile(fileOption.Filename)
if err != nil {
rErr = multierror.Append(rErr, errors.Wrapf(err, "failed to read advanced log file at path %s in %s", fileOption.Filename, name))
@ -296,7 +342,7 @@ func (ps *PlatformService) GetAdvancedLogs(_ request.CTX) ([]*model.FileData, er
}
}
return ret, nil
return ret, rErr.ErrorOrNil()
}
func isLogFilteredByLevel(logFilter *model.LogFilter, entry *model.LogEntry) bool {

View file

@ -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)
})
}

View file

@ -37,6 +37,9 @@ func TestGenerateSupportPacket(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)
th.Service.UpdateConfig(func(cfg *model.Config) {
*cfg.LogSettings.FileLocation = dir
*cfg.NotificationLogSettings.FileLocation = dir

View file

@ -36,6 +36,9 @@ func TestGenerateSupportPacket(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)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.LogSettings.FileLocation = dir
*cfg.NotificationLogSettings.FileLocation = dir

View file

@ -35,6 +35,7 @@ type MainHelper struct {
status int
testResourcePath string
testLogsPath string
replicas []string
storePool *sqlstore.TestPool
}
@ -88,6 +89,15 @@ func NewMainHelperWithOptions(options *HelperOptions) *MainHelper {
log.Fatal(err)
}
// Create a logs directory and set MM_LOG_PATH for tests that validate log file paths.
// This is done unconditionally so tests don't need to enable full resources just for logging.
logsDir, err := os.MkdirTemp("", "testlogs")
if err != nil {
log.Fatal("Failed to create test logs directory: " + err.Error())
}
os.Setenv("MM_LOG_PATH", logsDir)
mainHelper.testLogsPath = logsDir
if options != nil {
mainHelper.Options = *options
@ -293,6 +303,10 @@ func (h *MainHelper) Close() error {
if h.testResourcePath != "" {
os.RemoveAll(h.testResourcePath)
}
if h.testLogsPath != "" {
os.RemoveAll(h.testLogsPath)
os.Unsetenv("MM_LOG_PATH")
}
if h.storePool != nil {
h.storePool.Close()

View file

@ -142,6 +142,12 @@ func SetupTestResources() (string, error) {
return "", errors.Wrapf(err, "failed to create client directory %s", clientDir)
}
logsDir := path.Join(tempDir, "logs")
err = os.Mkdir(logsDir, 0700)
if err != nil {
return "", errors.Wrapf(err, "failed to create logs directory %s", logsDir)
}
err = setupConfig(path.Join(tempDir, "config"))
if err != nil {
return "", errors.Wrap(err, "failed to setup config")

View file

@ -6,11 +6,13 @@ package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/utils"
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
)
@ -127,6 +129,123 @@ func GetLogSettingsFromNotificationsLogSettings(notificationLogSettings *model.N
return settings
}
// GetLogRootPath returns the root directory for all log files.
// This is used for security validation to prevent arbitrary file reads via advanced logging.
// The logging root is determined by:
// 1. MM_LOG_PATH environment variable (if set and non-empty)
// 2. The default "logs" directory (found relative to the binary)
func GetLogRootPath() string {
// Check environment variable first
if envPath := os.Getenv("MM_LOG_PATH"); envPath != "" {
absPath, err := filepath.Abs(envPath)
if err == nil {
return absPath
}
}
// Fall back to default logs directory
logsDir, _ := fileutils.FindDir("logs")
absPath, err := filepath.Abs(logsDir)
if err != nil {
return logsDir
}
return absPath
}
// ValidateLogFilePath validates that a log file path is within the logging root directory.
// This prevents arbitrary file read/write vulnerabilities in logging configuration.
// The logging root is determined by MM_LOG_PATH environment variable or the configured log directory.
func ValidateLogFilePath(filePath string, loggingRoot string) error {
// Resolve file path to absolute
absPath, err := filepath.Abs(filePath)
if err != nil {
return fmt.Errorf("cannot resolve path %s: %w", filePath, err)
}
// Resolve symlinks to prevent bypass via symlink attacks
realPath, err := filepath.EvalSymlinks(absPath)
if err != nil {
// If file doesn't exist, still validate the intended path
if !os.IsNotExist(err) {
return fmt.Errorf("cannot resolve symlinks for %s: %w", absPath, err)
}
} else {
absPath = realPath
}
// Resolve logging root to absolute
absRoot, err := filepath.Abs(loggingRoot)
if err != nil {
return fmt.Errorf("cannot resolve logging root %s: %w", loggingRoot, err)
}
// Ensure root has trailing separator for proper prefix matching
// This prevents /tmp/log matching /tmp/logger
rootWithSep := absRoot
if !strings.HasSuffix(rootWithSep, string(filepath.Separator)) {
rootWithSep += string(filepath.Separator)
}
// Check if file is within the logging root
// Allow exact match (absPath == absRoot) or proper prefix match
if absPath != absRoot && !strings.HasPrefix(absPath, rootWithSep) {
return fmt.Errorf("path %s is outside logging root %s", filePath, absRoot)
}
return nil
}
// WarnIfLogPathsOutsideRoot validates log file paths in the config and logs errors for paths outside the logging root.
// This is called during config save to identify configurations that will cause server startup to fail in a future version.
// Currently only logs errors; in a future version this will block server startup.
func WarnIfLogPathsOutsideRoot(cfg *model.Config) {
loggingRoot := GetLogRootPath()
// Check LogSettings.AdvancedLoggingJSON
if !utils.IsEmptyJSON(cfg.LogSettings.AdvancedLoggingJSON) {
validateAdvancedLoggingConfig(cfg.LogSettings.AdvancedLoggingJSON, "LogSettings.AdvancedLoggingJSON", loggingRoot)
}
// Check NotificationLogSettings.AdvancedLoggingJSON
if !utils.IsEmptyJSON(cfg.NotificationLogSettings.AdvancedLoggingJSON) {
validateAdvancedLoggingConfig(cfg.NotificationLogSettings.AdvancedLoggingJSON, "NotificationLogSettings.AdvancedLoggingJSON", loggingRoot)
}
// Check ExperimentalAuditSettings.AdvancedLoggingJSON
if !utils.IsEmptyJSON(cfg.ExperimentalAuditSettings.AdvancedLoggingJSON) {
validateAdvancedLoggingConfig(cfg.ExperimentalAuditSettings.AdvancedLoggingJSON, "ExperimentalAuditSettings.AdvancedLoggingJSON", loggingRoot)
}
}
func validateAdvancedLoggingConfig(loggingJSON json.RawMessage, configName string, loggingRoot string) {
logCfg := make(mlog.LoggerConfiguration)
if err := json.Unmarshal(loggingJSON, &logCfg); err != nil {
return
}
for targetName, target := range logCfg {
if target.Type != "file" {
continue
}
var fileOption struct {
Filename string `json:"filename"`
}
if err := json.Unmarshal(target.Options, &fileOption); err != nil {
continue
}
if err := ValidateLogFilePath(fileOption.Filename, loggingRoot); err != nil {
mlog.Error("Log file path in logging config is outside logging root directory. This configuration will cause server startup to fail in a future version. To fix, set MM_LOG_PATH environment variable to a parent directory containing all log paths, or move log files to the configured logging root.",
mlog.String("config_section", configName),
mlog.String("target", targetName),
mlog.String("path", fileOption.Filename),
mlog.String("logging_root", loggingRoot),
mlog.Err(err))
}
}
}
func makeSimpleConsoleTarget(level string, outputJSON bool, color bool) (mlog.TargetCfg, error) {
levels, err := stdLevels(level)
if err != nil {

View file

@ -5,6 +5,8 @@ package config
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -50,3 +52,169 @@ func TestMloggerConfigFromAuditConfig(t *testing.T) {
assert.Equal(t, optionsExpected, optionsReceived)
})
}
func TestGetLogRootPath(t *testing.T) {
t.Run("returns MM_LOG_PATH when set", func(t *testing.T) {
// Create a temp directory to use as MM_LOG_PATH
dir, err := os.MkdirTemp("", "logroot")
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(dir)
})
t.Setenv("MM_LOG_PATH", dir)
result := GetLogRootPath()
absDir, _ := filepath.Abs(dir)
assert.Equal(t, absDir, result)
})
t.Run("finds logs directory relative to binary when MM_LOG_PATH not set", func(t *testing.T) {
// When MM_LOG_PATH is not set, GetLogRootPath falls back to FindDir("logs"),
// which searches for a "logs" directory relative to the working directory
// and the binary location. Create a logs directory relative to the test
// binary to verify this behavior.
t.Setenv("MM_LOG_PATH", "")
// Get the test binary location
exe, err := os.Executable()
require.NoError(t, err)
exe, err = filepath.EvalSymlinks(exe)
require.NoError(t, err)
binaryDir := filepath.Dir(exe)
// Create a "logs" directory next to the binary
logsDir := filepath.Join(binaryDir, "logs")
err = os.MkdirAll(logsDir, 0755)
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(logsDir)
})
result := GetLogRootPath()
// Result should be an absolute path
assert.True(t, filepath.IsAbs(result), "GetLogRootPath should return an absolute path, got: %s", result)
// FindDir searches working directory first, then binary directory.
// The result should be either the logs directory we created or another
// logs directory found earlier in the search path. Either way, it should
// be a valid directory path ending in "logs".
assert.True(t, filepath.Base(result) == "logs" || result == "./",
"GetLogRootPath should return a logs directory path, got: %s", result)
})
}
func TestValidateLogFilePath(t *testing.T) {
t.Run("valid path within root", func(t *testing.T) {
root, err := os.MkdirTemp("", "logroot")
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(root)
})
validFile := filepath.Join(root, "app.log")
err = os.WriteFile(validFile, []byte("test"), 0644)
require.NoError(t, err)
err = ValidateLogFilePath(validFile, root)
assert.NoError(t, err)
})
t.Run("valid path in subdirectory", func(t *testing.T) {
root, err := os.MkdirTemp("", "logroot")
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(root)
})
subdir := filepath.Join(root, "subdir")
err = os.MkdirAll(subdir, 0755)
require.NoError(t, err)
validFile := filepath.Join(subdir, "app.log")
err = os.WriteFile(validFile, []byte("test"), 0644)
require.NoError(t, err)
err = ValidateLogFilePath(validFile, root)
assert.NoError(t, err)
})
t.Run("rejects absolute path outside root", func(t *testing.T) {
root, err := os.MkdirTemp("", "logroot")
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(root)
})
outsideDir, err := os.MkdirTemp("", "outside")
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(outsideDir)
})
outsideFile := filepath.Join(outsideDir, "secret.txt")
err = os.WriteFile(outsideFile, []byte("secret"), 0644)
require.NoError(t, err)
err = ValidateLogFilePath(outsideFile, root)
assert.Error(t, err)
assert.Contains(t, err.Error(), "outside logging root")
})
t.Run("rejects path traversal attack", func(t *testing.T) {
root, err := os.MkdirTemp("", "logroot")
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(root)
})
traversalPath := filepath.Join(root, "..", "..", "etc", "passwd")
err = ValidateLogFilePath(traversalPath, root)
assert.Error(t, err)
assert.Contains(t, err.Error(), "outside logging root")
})
t.Run("rejects symlink pointing outside root", func(t *testing.T) {
root, err := os.MkdirTemp("", "logroot")
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(root)
})
outsideDir, err := os.MkdirTemp("", "outside")
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(outsideDir)
})
// Create a file outside the root
outsideFile := filepath.Join(outsideDir, "secret.txt")
err = os.WriteFile(outsideFile, []byte("secret"), 0644)
require.NoError(t, err)
// Create a symlink inside root pointing to the outside file
symlinkPath := filepath.Join(root, "sneaky.log")
err = os.Symlink(outsideFile, symlinkPath)
require.NoError(t, err)
err = ValidateLogFilePath(symlinkPath, root)
assert.Error(t, err)
assert.Contains(t, err.Error(), "outside logging root")
})
t.Run("allows non-existent file path within root", func(t *testing.T) {
root, err := os.MkdirTemp("", "logroot")
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(root)
})
// File doesn't exist but path is within root
nonExistentFile := filepath.Join(root, "future.log")
err = ValidateLogFilePath(nonExistentFile, root)
assert.NoError(t, err)
})
}

View file

@ -0,0 +1,473 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
// Access Control & Security
const (
AuditEventApplyIPFilters = "applyIPFilters" // apply IP address filtering
AuditEventAssignAccessPolicy = "assignAccessPolicy" // assign access control policy to channels
AuditEventCreateAccessControlPolicy = "createAccessControlPolicy" // create access control policy
AuditEventDeleteAccessControlPolicy = "deleteAccessControlPolicy" // delete access control policy
AuditEventUnassignAccessPolicy = "unassignAccessPolicy" // remove access control policy from channels
AuditEventUpdateActiveStatus = "updateActiveStatus" // update active/inactive status of access control policy
AuditEventSetActiveStatus = "setActiveStatus" // set active/inactive status of multiple access control policies
)
// Audit & Certificates
const (
AuditEventAddAuditLogCertificate = "addAuditLogCertificate" // add certificate for secure audit log transmission
AuditEventGetAudits = "getAudits" // get audit log entries
AuditEventGetUserAudits = "getUserAudits" // get audit log entries for specific user
AuditEventRemoveAuditLogCertificate = "removeAuditLogCertificate" // remove certificate used for audit log transmission
)
// Bots
const (
AuditEventAssignBot = "assignBot" // assign bot to user
AuditEventConvertBotToUser = "convertBotToUser" // convert bot account to regular user account
AuditEventConvertUserToBot = "convertUserToBot" // convert regular user account to bot account
AuditEventCreateBot = "createBot" // create bot account
AuditEventPatchBot = "patchBot" // update bot properties
AuditEventUpdateBotActive = "updateBotActive" // enable or disable bot account
)
// Branding
const (
AuditEventDeleteBrandImage = "deleteBrandImage" // delete brand image
AuditEventUploadBrandImage = "uploadBrandImage" // upload brand image
)
// Channel Bookmarks
const (
AuditEventCreateChannelBookmark = "createChannelBookmark" // create bookmark in channels
AuditEventDeleteChannelBookmark = "deleteChannelBookmark" // delete bookmark
AuditEventUpdateChannelBookmark = "updateChannelBookmark" // update bookmark
AuditEventUpdateChannelBookmarkSortOrder = "updateChannelBookmarkSortOrder" // update display order of bookmarks
AuditEventListChannelBookmarksForChannel = "listChannelBookmarksForChannel" // list bookmarks for channel
)
// Channel Categories
const (
AuditEventCreateCategoryForTeamForUser = "createCategoryForTeamForUser" // create channel category for user
AuditEventDeleteCategoryForTeamForUser = "deleteCategoryForTeamForUser" // delete channel category
AuditEventUpdateCategoriesForTeamForUser = "updateCategoriesForTeamForUser" // update multiple channel categories
AuditEventUpdateCategoryForTeamForUser = "updateCategoryForTeamForUser" // update single channel category
AuditEventUpdateCategoryOrderForTeamForUser = "updateCategoryOrderForTeamForUser" // update display order of the categories
)
// Channels
const (
AuditEventAddChannelMember = "addChannelMember" // add member to channel
AuditEventConvertGroupMessageToChannel = "convertGroupMessageToChannel" // convert group message to private channel
AuditEventCreateChannel = "createChannel" // create public or private channel
AuditEventCreateDirectChannel = "createDirectChannel" // create direct message channel between two users
AuditEventCreateGroupChannel = "createGroupChannel" // create group message channel with multiple users
AuditEventDeleteChannel = "deleteChannel" // delete channel
AuditEventGetPinnedPosts = "getPinnedPosts" // get pinned posts
AuditEventLocalAddChannelMember = "localAddChannelMember" // add channel member locally
AuditEventLocalCreateChannel = "localCreateChannel" // create channel locally
AuditEventLocalDeleteChannel = "localDeleteChannel" // delete channel locally
AuditEventLocalMoveChannel = "localMoveChannel" // move channel locally
AuditEventLocalPatchChannel = "localPatchChannel" // patch channel locally
AuditEventLocalRemoveChannelMember = "localRemoveChannelMember" // remove channel member locally
AuditEventLocalRestoreChannel = "localRestoreChannel" // restore channel locally
AuditEventLocalUpdateChannelPrivacy = "localUpdateChannelPrivacy" // update channel privacy locally
AuditEventMoveChannel = "moveChannel" // move channel to different team
AuditEventPatchChannel = "patchChannel" // update channel properties
AuditEventPatchChannelModerations = "patchChannelModerations" // update channel moderation settings
AuditEventRemoveChannelMember = "removeChannelMember" // remove member from channel
AuditEventRestoreChannel = "restoreChannel" // restore previously deleted channel
AuditEventUpdateChannel = "updateChannel" // update channel properties
AuditEventUpdateChannelMemberNotifyProps = "updateChannelMemberNotifyProps" // update notification preferences
AuditEventUpdateChannelMemberRoles = "updateChannelMemberRoles" // update roles and permissions
AuditEventUpdateChannelMemberSchemeRoles = "updateChannelMemberSchemeRoles" // update scheme-based roles
AuditEventUpdateChannelPrivacy = "updateChannelPrivacy" // change channel privacy settings
AuditEventUpdateChannelScheme = "updateChannelScheme" // update permission scheme applied to channel
)
// Commands
const (
AuditEventCreateCommand = "createCommand" // create slash command
AuditEventDeleteCommand = "deleteCommand" // delete command
AuditEventExecuteCommand = "executeCommand" // execute command
AuditEventLocalCreateCommand = "localCreateCommand" // create command locally
AuditEventMoveCommand = "moveCommand" // move command to another team
AuditEventRegenCommandToken = "regenCommandToken" // regenerate authentication token for command
AuditEventUpdateCommand = "updateCommand" // update command
)
// Compliance
const (
AuditEventCreateComplianceReport = "createComplianceReport" // create compliance report
AuditEventDownloadComplianceReport = "downloadComplianceReport" // download compliance report
AuditEventGetComplianceReport = "getComplianceReport" // get specific compliance report
AuditEventGetComplianceReports = "getComplianceReports" // get all compliance reports
)
// Configuration
const (
AuditEventConfigReload = "configReload" // reload server configuration
AuditEventGetConfig = "getConfig" // get current server configuration
AuditEventLocalGetClientConfig = "localGetClientConfig" // get client configuration locally
AuditEventLocalGetConfig = "localGetConfig" // get server configuration locally
AuditEventLocalPatchConfig = "localPatchConfig" // update server configuration locally
AuditEventLocalUpdateConfig = "localUpdateConfig" // update server configuration locally
AuditEventMigrateConfig = "migrateConfig" // migrate configs with file values from one store to another
AuditEventPatchConfig = "patchConfig" // update server configuration
AuditEventUpdateConfig = "updateConfig" // update server configuration
)
// Custom Profile Attributes
const (
AuditEventCreateCPAField = "createCPAField" // create custom profile attribute
AuditEventDeleteCPAField = "deleteCPAField" // delete custom profile attribute
AuditEventPatchCPAField = "patchCPAField" // update custom profile attribute field
AuditEventPatchCPAValues = "patchCPAValues" // update custom profile attribute values
)
// Data Retention Policies
const (
AuditEventAddChannelsToPolicy = "addChannelsToPolicy" // add channels to data retention policy
AuditEventAddTeamsToPolicy = "addTeamsToPolicy" // add teams to data retention policy
AuditEventCreatePolicy = "createPolicy" // create data retention policy
AuditEventDeletePolicy = "deletePolicy" // delete data retention policy
AuditEventPatchPolicy = "patchPolicy" // update data retention policy
AuditEventRemoveChannelsFromPolicy = "removeChannelsFromPolicy" // remove channels from data retention policy
AuditEventRemoveTeamsFromPolicy = "removeTeamsFromPolicy" // remove teams from data retention policy
)
// Emojis
const (
AuditEventCreateEmoji = "createEmoji" // create emoji
AuditEventDeleteEmoji = "deleteEmoji" // delete emoji
)
// Exports
const (
AuditEventBulkExport = "bulkExport" // bulk export data to a file
AuditEventDeleteExport = "deleteExport" // delete exported file
AuditEventGeneratePresignURLExport = "generatePresignURLExport" // generate presigned URL to download the exported file
AuditEventScheduleExport = "scheduleExport" // schedule export job
)
// Files
const (
AuditEventGetFile = "getFile" // get or download file
AuditEventGetFileLink = "getFileLink" // generate link for file sharing
AuditEventUploadFileMultipart = "uploadFileMultipart" // upload file using multipart form data
AuditEventUploadFileMultipartLegacy = "uploadFileMultipartLegacy" // upload file using legacy multipart method
AuditEventUploadFileSimple = "uploadFileSimple" // upload file using simple direct upload method
AuditEventGetFileThumbnail = "getFileThumbnail" // get file thumbnail
AuditEventGetFileInfosForPost = "getFileInfosForPost" // get file infos for post
AuditEventGetFileInfo = "getFileInfo" // get file info
AuditEventGetFilePreview = "getFilePreview" // get file preview
AuditEventSearchFiles = "searchFiles" // search for files
)
// Groups
const (
AuditEventAddGroupMembers = "addGroupMembers" // add members to group
AuditEventAddUserToGroupSyncables = "addUserToGroupSyncables" // add user to group-synchronized teams and channels
AuditEventCreateGroup = "createGroup" // create group
AuditEventDeleteGroup = "deleteGroup" // delete group
AuditEventDeleteGroupMembers = "deleteGroupMembers" // remove members from group
AuditEventLinkGroupSyncable = "linkGroupSyncable" // link group to team or channel for synchronization
AuditEventPatchGroup = "patchGroup" // update group
AuditEventPatchGroupSyncable = "patchGroupSyncable" // update group synchronization settings
AuditEventRestoreGroup = "restoreGroup" // restore previously deleted group
AuditEventUnlinkGroupSyncable = "unlinkGroupSyncable" // unlink group from team or channel synchronization
)
// Imports
const (
AuditEventBulkImport = "bulkImport" // bulk import data from a file
AuditEventDeleteImport = "deleteImport" // delete import file
AuditEventSlackImport = "slackImport" // import data from Slack
)
// Jobs
const (
AuditEventCancelJob = "cancelJob" // cancel a job
AuditEventCreateJob = "createJob" // create a job
AuditEventJobServer = "jobServer" // start job server
AuditEventUpdateJobStatus = "updateJobStatus" // update status of a job
)
// LDAP
const (
AuditEventAddLdapPrivateCertificate = "addLdapPrivateCertificate" // add private certificate for LDAP
AuditEventAddLdapPublicCertificate = "addLdapPublicCertificate" // add public certificate for LDAP
AuditEventIdMigrateLdap = "idMigrateLdap" // migrate user ID mapping to another attribute
AuditEventLinkLdapGroup = "linkLdapGroup" // link LDAP group to Mattermost team or channel
AuditEventRemoveLdapPrivateCertificate = "removeLdapPrivateCertificate" // remove private certificate for LDAP
AuditEventRemoveLdapPublicCertificate = "removeLdapPublicCertificate" // remove public certificate for LDAP
AuditEventSyncLdap = "syncLdap" // synchronize users and groups from LDAP
AuditEventUnlinkLdapGroup = "unlinkLdapGroup" // unlink LDAP group from Mattermost team or channel
)
// Licensing
const (
AuditEventAddLicense = "addLicense" // add license
AuditEventLocalAddLicense = "localAddLicense" // add license locally
AuditEventLocalRemoveLicense = "localRemoveLicense" // remove license locally
AuditEventRemoveLicense = "removeLicense" // remove license
AuditEventRequestTrialLicense = "requestTrialLicense" // request trial license
)
// OAuth
const (
AuditEventAuthorizeOAuthApp = "authorizeOAuthApp" // authorize OAuth app
AuditEventAuthorizeOAuthPage = "authorizeOAuthPage" // authorize OAuth page
AuditEventCompleteOAuth = "completeOAuth" // complete OAuth authorization flow
AuditEventCreateOAuthApp = "createOAuthApp" // create OAuth app
AuditEventCreateOutgoingOauthConnection = "createOutgoingOauthConnection" // create outgoing OAuth connection
AuditEventDeauthorizeOAuthApp = "deauthorizeOAuthApp" // revoke OAuth app authorization
AuditEventDeleteOAuthApp = "deleteOAuthApp" // delete OAuth app
AuditEventDeleteOutgoingOAuthConnection = "deleteOutgoingOAuthConnection" // delete outgoing OAuth connection
AuditEventGetAccessToken = "getAccessToken" // get OAuth access token
AuditEventLoginWithOAuth = "loginWithOAuth" // login using OAuth authentication provider
AuditEventMobileLoginWithOAuth = "mobileLoginWithOAuth" // mobile application login using OAuth authentication provider
AuditEventRegenerateOAuthAppSecret = "regenerateOAuthAppSecret" // regenerate secret key for OAuth app
AuditEventRegisterOAuthClient = "registerOAuthClient" // register OAuth client via dynamic client registration (RFC 7591)
AuditEventSignupWithOAuth = "signupWithOAuth" // create account using OAuth authentication provider
AuditEventUpdateOAuthApp = "updateOAuthApp" // update OAuth app
AuditEventUpdateOutgoingOAuthConnection = "updateOutgoingOAuthConnection" // update outgoing OAuth connection
AuditEventValidateOutgoingOAuthConnectionCredentials = "validateOutgoingOAuthConnectionCredentials" // validate credentials for outgoing OAuth connection
)
// Plugins
const (
AuditEventDisablePlugin = "disablePlugin" // disable installed plugin
AuditEventEnablePlugin = "enablePlugin" // enable installed plugin
AuditEventGetFirstAdminVisitMarketplaceStatus = "getFirstAdminVisitMarketplaceStatus" // get first admin visit status
AuditEventInstallMarketplacePlugin = "installMarketplacePlugin" // install plugin from official marketplace
AuditEventInstallPluginFromURL = "installPluginFromURL" // install plugin from external URL
AuditEventRemovePlugin = "removePlugin" // delete plugin
AuditEventSetFirstAdminVisitMarketplaceStatus = "setFirstAdminVisitMarketplaceStatus" // set first admin visit status
AuditEventUploadPlugin = "uploadPlugin" // upload plugin file to server for installation
)
// Posts
const (
AuditEventCreateEphemeralPost = "createEphemeralPost" // create ephemeral post
AuditEventCreatePost = "createPost" // create post
AuditEventDeletePost = "deletePost" // delete post
AuditEventGetEditHistoryForPost = "getEditHistoryForPost" // get edit history for post
AuditEventGetFlaggedPosts = "getFlaggedPosts" // get flagged posts
AuditEventGetPostsForChannel = "getPostsForChannel" // get posts for channel
AuditEventGetPostsForChannelAroundLastUnread = "getPostsForChannelAroundLastUnread" // get posts for channel around last unread
AuditEventGetPost = "getPost" // get post
AuditEventGetPostThread = "getPostThread" // get post thread
AuditEventGetPostsByIds = "getPostsByIds" // get posts by ids
AuditEventGetThreadForUser = "getThreadForUser" // get thread for user
AuditEventLocalDeletePost = "localDeletePost" // delete post locally
AuditEventMoveThread = "moveThread" // move thread and replies to different channel
AuditEventNotificationAck = "notificationAck" // notification ack
AuditEventPatchPost = "patchPost" // update post meta properties
AuditEventRestorePostVersion = "restorePostVersion" // restore post to previous version
AuditEventSaveIsPinnedPost = "saveIsPinnedPost" // pin or unpin post
AuditEventSearchPosts = "searchPosts" // search for posts
AuditEventUpdatePost = "updatePost" // update post content
AuditEventRevealPost = "revealPost" // reveal a post that was hidden due to burn on read
AuditEventBurnPost = "burnPost" // burn a post that was hidden due to burn on read
AuditEventWebsocketPost = "websocketPost" // post received via websocket
)
// Recaps
const (
AuditEventCreateRecap = "createRecap" // create recap summarizing channel content
AuditEventGetRecap = "getRecap" // view a single recap
AuditEventGetRecaps = "getRecaps" // list user's recaps
AuditEventMarkRecapAsRead = "markRecapAsRead" // mark recap as read
AuditEventRegenerateRecap = "regenerateRecap" // regenerate recap with updated channel content
AuditEventDeleteRecap = "deleteRecap" // delete recap
)
// Preferences
const (
AuditEventDeletePreferences = "deletePreferences" // delete user preferences
AuditEventUpdatePreferences = "updatePreferences" // update user preferences
)
// Remote Clusters
const (
AuditEventCreateRemoteCluster = "createRemoteCluster" // create connection to remote Mattermost cluster
AuditEventDeleteRemoteCluster = "deleteRemoteCluster" // delete connection to remote Mattermost cluster
AuditEventGenerateRemoteClusterInvite = "generateRemoteClusterInvite" // generate invitation token for remote cluster connection
AuditEventInviteRemoteClusterToChannel = "inviteRemoteClusterToChannel" // invite remote cluster users to shared channel
AuditEventPatchRemoteCluster = "patchRemoteCluster" // update remote cluster connection settings
AuditEventRemoteClusterAcceptInvite = "remoteClusterAcceptInvite" // accept invitation from remote cluster
AuditEventRemoteClusterAcceptMessage = "remoteClusterAcceptMessage" // accept message from remote cluster
AuditEventRemoteUploadProfileImage = "remoteUploadProfileImage" // upload profile image from remote cluster
AuditEventUninviteRemoteClusterToChannel = "uninviteRemoteClusterToChannel" // remove remote cluster access from shared channel
AuditEventUploadRemoteData = "uploadRemoteData" // upload data to remote cluster
)
// Roles
const (
AuditEventPatchRole = "patchRole" // update role permissions
)
// SAML
const (
AuditEventAddSamlIdpCertificate = "addSamlIdpCertificate" // add SAML identity provider certificate
AuditEventAddSamlPrivateCertificate = "addSamlPrivateCertificate" // add SAML private certificate
AuditEventAddSamlPublicCertificate = "addSamlPublicCertificate" // add SAML public certificate
AuditEventCompleteSaml = "completeSaml" // complete SAML authentication flow
AuditEventRemoveSamlIdpCertificate = "removeSamlIdpCertificate" // remove SAML identity provider certificate
AuditEventRemoveSamlPrivateCertificate = "removeSamlPrivateCertificate" // remove SAML private certificate
AuditEventRemoveSamlPublicCertificate = "removeSamlPublicCertificate" // remove SAML public certificate
)
// Scheduled Posts
const (
AuditEventCreateSchedulePost = "createSchedulePost" // create post scheduled for future delivery
AuditEventDeleteScheduledPost = "deleteScheduledPost" // delete scheduled post before delivery
AuditEventUpdateScheduledPost = "updateScheduledPost" // update scheduled post
)
// Schemes
const (
AuditEventCreateScheme = "createScheme" // create permission scheme with role definitions
AuditEventDeleteScheme = "deleteScheme" // delete scheme
AuditEventPatchScheme = "patchScheme" // update scheme
)
// Search Indexes
const (
AuditEventPurgeBleveIndexes = "purgeBleveIndexes" // purge Bleve search indexes
AuditEventPurgeElasticsearchIndexes = "purgeElasticsearchIndexes" // purge Elasticsearch search indexes
)
// Server Administration
const (
AuditEventClearServerBusy = "clearServerBusy" // clear server busy status to allow normal operations
AuditEventCompleteOnboarding = "completeOnboarding" // complete system onboarding process
AuditEventDatabaseRecycle = "databaseRecycle" // closes active connections
AuditEventDownloadLogs = "downloadLogs" // download server log files
AuditEventGenerateSupportPacket = "generateSupportPacket" // generate support packet with server diagnostics and logs
AuditEventGetAppliedSchemaMigrations = "getAppliedSchemaMigrations" // get list of applied database schema migrations
AuditEventGetLogs = "getLogs" // get server log entries
AuditEventGetOnboarding = "getOnboarding" // get system onboarding status
AuditEventInvalidateCaches = "invalidateCaches" // clear server caches
AuditEventLocalCheckIntegrity = "localCheckIntegrity" // check database integrity locally
AuditEventQueryLogs = "queryLogs" // search server log entries
AuditEventRestartServer = "restartServer" // restart Mattermost server process
AuditEventSetServerBusy = "setServerBusy" // set server busy status to disallow any operations
AuditEventUpdateViewedProductNotices = "updateViewedProductNotices" // update viewed status of product notices
AuditEventUpgradeToEnterprise = "upgradeToEnterprise" // upgrade server to Enterprise edition
)
// Teams
const (
AuditEventAddTeamMember = "addTeamMember" // add member to team
AuditEventAddTeamMembers = "addTeamMembers" // add multiple members to team
AuditEventAddUserToTeamFromInvite = "addUserToTeamFromInvite" // add user to team using invitation link
AuditEventCreateTeam = "createTeam" // create team
AuditEventDeleteTeam = "deleteTeam" // delete team
AuditEventImportTeam = "importTeam" // import team data from external source
AuditEventInvalidateAllEmailInvites = "invalidateAllEmailInvites" // invalidate all pending email invitations
AuditEventInviteGuestsToChannels = "inviteGuestsToChannels" // invite guest users to specific channels
AuditEventInviteUsersToTeam = "inviteUsersToTeam" // invite users to team
AuditEventLocalCreateTeam = "localCreateTeam" // create team locally
AuditEventLocalDeleteTeam = "localDeleteTeam" // delete team locally
AuditEventLocalInviteUsersToTeam = "localInviteUsersToTeam" // invite users to team locally
AuditEventPatchTeam = "patchTeam" // update team properties
AuditEventRegenerateTeamInviteId = "regenerateTeamInviteId" // regenerate team invitation ID
AuditEventRemoveTeamIcon = "removeTeamIcon" // remove custom icon from team
AuditEventRemoveTeamMember = "removeTeamMember" // remove member from team
AuditEventRestoreTeam = "restoreTeam" // restore previously deleted team
AuditEventSetTeamIcon = "setTeamIcon" // set custom icon for team
AuditEventUpdateTeam = "updateTeam" // update team properties
AuditEventUpdateTeamMemberRoles = "updateTeamMemberRoles" // update roles of team members
AuditEventUpdateTeamMemberSchemeRoles = "updateTeamMemberSchemeRoles" // update scheme-based roles of team members
AuditEventUpdateTeamPrivacy = "updateTeamPrivacy" // change team privacy settings
AuditEventUpdateTeamScheme = "updateTeamScheme" // update scheme applied to team
)
// Terms of Service
const (
AuditEventCreateTermsOfService = "createTermsOfService" // create terms of service
AuditEventSaveUserTermsOfService = "saveUserTermsOfService" // save user acceptance of terms of service
)
// Threads
const (
AuditEventFollowThreadByUser = "followThreadByUser" // follow thread to receive notifications about replies
AuditEventSetUnreadThreadByPostId = "setUnreadThreadByPostId" // mark thread as unread for user by post ID
AuditEventUnfollowThreadByUser = "unfollowThreadByUser" // unfollow thread to stop receiving notifications about replies
AuditEventUpdateReadStateAllThreadsByUser = "updateReadStateAllThreadsByUser" // update read status for all threads for user
AuditEventUpdateReadStateThreadByUser = "updateReadStateThreadByUser" // update read status for specific thread for user
)
// Uploads
const (
AuditEventCreateUpload = "createUpload" // create file upload session
AuditEventUploadData = "uploadData" // upload file data to server storage
)
// Users
const (
AuditEventAttachDeviceId = "attachDeviceId" // attach device ID to user session for mobile app
AuditEventCreateUser = "createUser" // create user account
AuditEventCreateUserAccessToken = "createUserAccessToken" // create personal access token for user API access
AuditEventDeleteUser = "deleteUser" // delete user account
AuditEventDemoteUserToGuest = "demoteUserToGuest" // demote regular user to guest account with limited permissions
AuditEventDisableUserAccessToken = "disableUserAccessToken" // disable user personal access token
AuditEventEnableUserAccessToken = "enableUserAccessToken" // enable user personal access token
AuditEventExtendSessionExpiry = "extendSessionExpiry" // extend user session expiration time
AuditEventLocalDeleteUser = "localDeleteUser" // delete user locally
AuditEventLocalPermanentDeleteAllUsers = "localPermanentDeleteAllUsers" // permanently delete all users locally
AuditEventLogin = "login" // user login to system
AuditEventLoginWithDesktopToken = "loginWithDesktopToken" // user login to system with desktop token
AuditEventLogout = "logout" // user logout from system
AuditEventMigrateAuthToLdap = "migrateAuthToLdap" // migrate user authentication method to LDAP
AuditEventMigrateAuthToSaml = "migrateAuthToSaml" // migrate user authentication method to SAML
AuditEventPatchUser = "patchUser" // update user properties
AuditEventPromoteGuestToUser = "promoteGuestToUser" // promote guest account to regular user
AuditEventResetPassword = "resetPassword" // reset user password
AuditEventResetPasswordFailedAttempts = "resetPasswordFailedAttempts" // reset failed password attempt counter
AuditEventRevokeAllSessionsAllUsers = "revokeAllSessionsAllUsers" // revoke all active sessions for all users
AuditEventRevokeAllSessionsForUser = "revokeAllSessionsForUser" // revoke all active sessions for specific user
AuditEventRevokeSession = "revokeSession" // revoke specific user session
AuditEventRevokeUserAccessToken = "revokeUserAccessToken" // revoke user personal access token
AuditEventSendPasswordReset = "sendPasswordReset" // send password reset email to user
AuditEventSendVerificationEmail = "sendVerificationEmail" // send email verification link to user
AuditEventSetDefaultProfileImage = "setDefaultProfileImage" // set user profile image to default avatar
AuditEventSetProfileImage = "setProfileImage" // set custom profile image for user
AuditEventSwitchAccountType = "switchAccountType" // switch user authentication method from one to another
AuditEventUpdatePassword = "updatePassword" // update user password
AuditEventUpdateUser = "updateUser" // update user account properties
AuditEventUpdateUserActive = "updateUserActive" // update user active status
AuditEventUpdateUserAuth = "updateUserAuth" // update user authentication method
AuditEventUpdateUserMfa = "updateUserMfa" // update user multi-factor authentication settings
AuditEventUpdateUserRoles = "updateUserRoles" // update user roles
AuditEventVerifyUserEmail = "verifyUserEmail" // verify user email address using verification token
AuditEventVerifyUserEmailWithoutToken = "verifyUserEmailWithoutToken" // verify user email address without verification token
)
// Webhooks
const (
AuditEventCreateIncomingHook = "createIncomingHook" // create incoming webhook
AuditEventCreateOutgoingHook = "createOutgoingHook" // create outgoing webhook
AuditEventDeleteIncomingHook = "deleteIncomingHook" // delete incoming webhook
AuditEventDeleteOutgoingHook = "deleteOutgoingHook" // delete outgoing webhook
AuditEventGetIncomingHook = "getIncomingHook" // get incoming webhook details
AuditEventGetOutgoingHook = "getOutgoingHook" // get outgoing webhook details
AuditEventLocalCreateIncomingHook = "localCreateIncomingHook" // create incoming webhook locally
AuditEventRegenOutgoingHookToken = "regenOutgoingHookToken" // regenerate authentication token
AuditEventUpdateIncomingHook = "updateIncomingHook" // update incoming webhook
AuditEventUpdateOutgoingHook = "updateOutgoingHook" // update outgoing webhook
)
// Content Flagging
const (
AuditEventFlagPost = "flagPost" // flag post for review
AuditEventGetFlaggedPost = "getFlaggedPost" // get flagged post details
AuditEventPermanentlyRemoveFlaggedPost = "permanentlyRemoveFlaggedPost" // permanently remove flagged post
AuditEventKeepFlaggedPost = "keepFlaggedPost" // keep flagged post
AuditEventUpdateContentFlaggingConfig = "updateContentFlaggingConfig" // update content flagging configuration
AuditEventSetReviewer = "setFlaggedPostReviewer" // assign reviewer for flagged post
)