Exec Guardian
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
execguard/execguard.go

231 lines
6.5 KiB

package main
// Copyright (c) 2025 Robert Strutts <bobs@NewToFaith.com>
// License: MIT
// GIT: https://git.mysnippetsofcode.com/bobs/execguard
import (
"execguard/core/alert"
"execguard/core/configure"
"execguard/core/rotating_logger"
"execguard/core/hasher"
"execguard/core/make_key"
"execguard/core/monitor_running_bins"
"execguard/core/new_file_monitor"
"execguard/core/scanner"
"execguard/core/sys_database"
"database/sql"
"flag"
"log"
"strings"
"os"
"fmt"
"time"
"path/filepath"
_ "github.com/mattn/go-sqlite3"
)
const (
configFileDefault = "/etc/execguard/config.yaml"
dbFileDefault = "/etc/execguard/system.db"
logFileDefault = "/var/log/execguard.log"
mailPathDefault = "/usr/bin/mail"
clamscanDefault = "/usr/bin/clamscan"
scanIntervalDefault = 0 // Disabled
)
var (
downloadsDefault []string
downloads []string
configFile string
dbFile string
logFile string
mailPath string
clamscanPath string
scanIntervalFlag int
downloadsFlag string
configFlag string
dbFlag string
logFlag string
mailFlag string
clamscanFlag string
initMode bool
enforceMode bool
initFile string
updateFile string
migrateMode bool
newKey bool
dirs []string
scanInterval int
config *configure.Config
)
func main() {
var err error
flag.IntVar(&scanIntervalFlag, "scanDelayMinutes", 99, "0 disables scanner")
flag.StringVar(&downloadsFlag, "downloads", "none", "use specified Downloads folders comma-seperated list")
flag.StringVar(&configFlag, "config", "", "use specified file for config")
flag.StringVar(&dbFlag, "db", "", "use specified file for database")
flag.StringVar(&logFlag, "log", "", "use specified file for Logging")
flag.StringVar(&mailFlag, "mail", "", "use specified file for Mail sending")
flag.StringVar(&clamscanFlag, "scanner", "", "use specified binary for Virus Scanning")
flag.BoolVar(&initMode, "init", false, "initialize and populate allowed executable database")
flag.BoolVar(&enforceMode, "enforce", false, "enforce policies from Database Lock-down...")
flag.StringVar(&initFile, "initFile", "", "file containing files to add to allowed database with hash")
flag.StringVar(&updateFile, "update", "", "add specified file to allowed database with hash")
flag.BoolVar(&migrateMode, "migrate", false, "recompute hashes of all allowed paths using current settings")
flag.BoolVar(&newKey, "newKey", false, "generate a new XXTEA-compatible encryption key")
flag.Parse()
if newKey {
if make_key.Make_a_key() {
return
} else {
os.Exit(1) // Exit with status code 1
}
}
if os.Geteuid() != 0 {
fmt.Printf("This program must be run as root")
os.Exit(1) // Exit with status code 1
}
scanner.SetModes(initMode, initFile, updateFile, migrateMode)
monitor_running_bins.SetModes(initMode, initFile, updateFile, migrateMode)
sys_database.SetModes(initMode, initFile, updateFile, migrateMode)
if configFlag != "" {
configFile = configFlag
} else {
configFile = configFileDefault
}
if logFlag != "" {
logFile = logFlag
} else {
logFile = logFileDefault
}
config, err := configure.LoadConfig(configFile, logFile)
if err != nil {
fmt.Printf("Error loading config: %v", err)
os.Exit(3) // Exit with status code 3
}
hasher.SetGlobalConfig(*config)
alert.SetGlobalConfig(*config)
monitor_running_bins.SetGlobalConfig(*config)
scanner.SetGlobalConfig(*config)
// Set Vars...arguemtns first, then config, then defaults
if dbFlag != "" {
dbFile = dbFlag
} else if config.DbFile != "" {
dbFile = config.DbFile
} else {
dbFile = dbFileDefault
}
if mailFlag != "" {
mailPath = mailFlag
} else if config.MailProg != "" {
mailPath = config.MailProg
} else {
mailPath = mailPathDefault
}
alert.SetGlobalMail(mailPath)
if clamscanFlag != "" {
clamscanPath = clamscanFlag
} else if config.ScannerProg != "" {
clamscanPath = config.ScannerProg
} else {
clamscanPath = clamscanDefault
}
if scanIntervalFlag != 99 {
scanInterval = scanIntervalFlag
} else if config.ScanInterval != 99 {
scanInterval = config.ScanInterval
} else {
scanInterval = scanIntervalDefault
}
if downloadsFlag != "none" {
downloads = strings.Split(downloadsFlag, ",")
}
if len(downloads) > 0 {
dirs = downloads
} else if len(config.Downloads) > 0 {
dirs = config.Downloads
} else {
dirs = downloadsDefault
}
rl, err := rotating_logger.NewRotatingLogger(config.Logging)
if err != nil {
fmt.Printf("Failed to initialize logger: %v", err)
}
defer rl.Close()
logger := log.New(rl, "", 0)
logger.SetPrefix(fmt.Sprintf("[%s] ", time.Now().Format(config.Logging.TimestampFormat)))
db, err := sql.Open("sqlite3", dbFile)
if err != nil {
logger.Fatalf("Error opening database: %v", err)
os.Exit(2) // Exit with status code 2
}
defer db.Close()
sys_database.CreateTable(db, logger)
if initFile != "" {
absPath, err := filepath.Abs(initFile)
if err != nil {
logger.Fatalf("Invalid init file path: %v", err)
os.Exit(1) // Exit with status code 1
}
sys_database.RunInit(db, logger, absPath)
return
}
if updateFile != "" {
absPath, err := filepath.Abs(updateFile)
if err != nil {
logger.Fatalf("Invalid update file path: %v", err)
os.Exit(1) // Exit with status code 1
}
sys_database.AddToAllowed(db, logger, absPath)
logger.Printf("Added to allowed list: %s", absPath)
return
}
if migrateMode {
sys_database.RunMigration(db, logger)
return
}
if scanInterval > 0 {
go func() {
defer func() {
if r := recover(); r != nil {
logger.Printf("Recovered from scan panic: %v", r)
}
}()
scanner.PeriodicScan(config.ProtectedDirs, db, logger, mailPath, scanInterval)
}()
}
if len(dirs) > 0 {
go new_file_monitor.Monitor_new_files(dirs, db, logger, clamscanPath)
}
if err := monitor_running_bins.MonitorExecutions(db, logger, mailPath); err != nil {
logger.Fatalf("Execution monitoring failed: %v", err)
os.Exit(4) // Exit with status code 4
}
}