Compare commits
3 Commits
74dce294bf
...
8615c79f12
| Author | SHA1 | Date | |
|---|---|---|---|
|
8615c79f12
|
|||
|
b5686a2ee6
|
|||
|
e78685c130
|
47
assets/configs/analyzer.toml
Normal file
47
assets/configs/analyzer.toml
Normal file
@@ -0,0 +1,47 @@
|
||||
###############################################################################
|
||||
# РАЗДЕЛ:Отслеживать авторизаций
|
||||
# ***
|
||||
# SECTION:Track authorizations
|
||||
###############################################################################
|
||||
[login]
|
||||
###
|
||||
# Включает группу отслеживания авторизации.
|
||||
# Если отключено, отслеживание авторизации работать не будет.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables the authorization tracking group.
|
||||
# If disabled, no authorization tracking will work.
|
||||
# Default: true
|
||||
###
|
||||
enabled = true
|
||||
|
||||
###
|
||||
# Включает уведомления об авторизации.
|
||||
# Если отключено, они будут отображаться в логах только на уровне = "info".
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables authorization notifications.
|
||||
# If disabled, they will only appear in the logs under level = "info".
|
||||
# Default: true
|
||||
###
|
||||
notify = true
|
||||
|
||||
###
|
||||
# Включает отслеживание авторизации по ssh.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables tracking of SSH authorization.
|
||||
# Default: true
|
||||
###
|
||||
ssh_enable = true
|
||||
|
||||
###
|
||||
# Включает уведомления об авторизации по ssh.
|
||||
# Если отключено, они будут отображаться в логах только на уровне = "info".
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables SSH authorization notifications.
|
||||
# If disabled, they will only appear in the logs under level = "info".
|
||||
# Default: true
|
||||
###
|
||||
ssh_notify = true
|
||||
@@ -192,6 +192,15 @@ log_error_paths = ["stderr"]
|
||||
###
|
||||
nftables = "/usr/sbin/nft"
|
||||
|
||||
###
|
||||
# Укажите путь к journalctl. Возможно в вашей ОС путь может отличаться.
|
||||
# По умолчанию: /bin/journalctl
|
||||
# ***
|
||||
# Specify the path to journalctl. The path may differ in your OS.
|
||||
# Default: /bin/journalctl
|
||||
###
|
||||
journalctl = "/bin/journalctl"
|
||||
|
||||
###############################################################################
|
||||
# РАЗДЕЛ:Пути к другим настройкам
|
||||
# ***
|
||||
@@ -221,3 +230,14 @@ firewall = "/etc/kor-elf-shield/firewall.toml"
|
||||
# Default: /etc/kor-elf-shield/notifications.toml
|
||||
###
|
||||
notifications = "/etc/kor-elf-shield/notifications.toml"
|
||||
|
||||
###
|
||||
# Укажите путь к настройкам парсинга логов.
|
||||
# Файл должен иметь расширение .toml.
|
||||
# По умолчанию: /etc/kor-elf-shield/analyzer.toml
|
||||
# ***
|
||||
# Specify the path to the log parsing settings.
|
||||
# The file must have the .toml extension.
|
||||
# Default: /etc/kor-elf-shield/analyzer.toml
|
||||
###
|
||||
analyzer = "/etc/kor-elf-shield/analyzer.toml"
|
||||
|
||||
@@ -35,17 +35,6 @@ func runDaemon(ctx context.Context, _ *cli.Command) error {
|
||||
_ = logger.Sync()
|
||||
}()
|
||||
|
||||
notificationsConfig, err := setting.Config.OtherSettingsPath.ToNotificationsConfig()
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
|
||||
// Fatal should call os.Exit(1), but there's a chance that might not happen,
|
||||
// so we return err just in case.
|
||||
return err
|
||||
}
|
||||
|
||||
notificationsClient := notifications.New(notificationsConfig, logger)
|
||||
|
||||
config, err := setting.Config.ToDaemonOptions()
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
@@ -55,7 +44,16 @@ func runDaemon(ctx context.Context, _ *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
d, err := daemon.NewDaemon(config, logger, notificationsClient)
|
||||
notificationsService, err := newNotificationsService(logger)
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
|
||||
// Fatal should call os.Exit(1), but there's a chance that might not happen,
|
||||
// so we return err just in case.return err
|
||||
return err
|
||||
}
|
||||
|
||||
d, err := daemon.NewDaemon(config, logger, notificationsService)
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
|
||||
@@ -75,3 +73,12 @@ func runDaemon(ctx context.Context, _ *cli.Command) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newNotificationsService(logger log.Logger) (notifications.Notifications, error) {
|
||||
config, err := setting.Config.OtherSettingsPath.ToNotificationsConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return notifications.New(config, logger), nil
|
||||
}
|
||||
|
||||
81
internal/daemon/analyzer/analyzer.go
Normal file
81
internal/daemon/analyzer/analyzer.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
config2 "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config"
|
||||
analyzerLog "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log"
|
||||
analysisServices "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/notifications"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
)
|
||||
|
||||
type Analyzer interface {
|
||||
Run(ctx context.Context)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type analyzer struct {
|
||||
config config2.Config
|
||||
logger log.Logger
|
||||
notify notifications.Notifications
|
||||
systemd analyzerLog.Systemd
|
||||
analysis analyzerLog.Analysis
|
||||
}
|
||||
|
||||
func New(config config2.Config, logger log.Logger, notify notifications.Notifications) Analyzer {
|
||||
units := []string{}
|
||||
if config.Login.Enabled && config.Login.SSH.Enabled {
|
||||
units = append(units, "ssh")
|
||||
}
|
||||
|
||||
systemdService := analyzerLog.NewSystemd(config.BinPath.Journalctl, units, logger)
|
||||
analysisService := analyzerLog.NewAnalysis(&config, logger, notify)
|
||||
|
||||
return &analyzer{
|
||||
config: config,
|
||||
logger: logger,
|
||||
notify: notify,
|
||||
systemd: systemdService,
|
||||
analysis: analysisService,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *analyzer) Run(ctx context.Context) {
|
||||
logChan := make(chan analysisServices.Entry, 1000)
|
||||
|
||||
go a.systemd.Run(ctx, logChan)
|
||||
go a.processLogs(ctx, logChan)
|
||||
a.logger.Debug("Analyzer is start")
|
||||
}
|
||||
|
||||
func (a *analyzer) processLogs(ctx context.Context, logChan <-chan analysisServices.Entry) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case entry := <-logChan:
|
||||
a.logger.Debug(fmt.Sprintf("Received log entry: %s", entry))
|
||||
switch entry.Unit {
|
||||
case "ssh.service":
|
||||
if err := a.analysis.SSH(&entry); err != nil {
|
||||
a.logger.Error(fmt.Sprintf("Failed to analyze SSH logs: %s", err))
|
||||
}
|
||||
break
|
||||
default:
|
||||
a.logger.Warn(fmt.Sprintf("Unknown unit: %s", entry.Unit))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *analyzer) Close() error {
|
||||
if err := a.systemd.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.logger.Debug("Analyzer is stop")
|
||||
|
||||
return nil
|
||||
}
|
||||
5
internal/daemon/analyzer/config/bin.go
Normal file
5
internal/daemon/analyzer/config/bin.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package config
|
||||
|
||||
type BinPath struct {
|
||||
Journalctl string
|
||||
}
|
||||
6
internal/daemon/analyzer/config/config.go
Normal file
6
internal/daemon/analyzer/config/config.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package config
|
||||
|
||||
type Config struct {
|
||||
BinPath BinPath
|
||||
Login Login
|
||||
}
|
||||
12
internal/daemon/analyzer/config/login.go
Normal file
12
internal/daemon/analyzer/config/login.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package config
|
||||
|
||||
type Login struct {
|
||||
Enabled bool
|
||||
Notify bool
|
||||
SSH LoginSSH
|
||||
}
|
||||
|
||||
type LoginSSH struct {
|
||||
Enabled bool
|
||||
Notify bool
|
||||
}
|
||||
31
internal/daemon/analyzer/log/analysis.go
Normal file
31
internal/daemon/analyzer/log/analysis.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config"
|
||||
analysisServices "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/notifications"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
)
|
||||
|
||||
type Analysis interface {
|
||||
SSH(entry *analysisServices.Entry) error
|
||||
}
|
||||
|
||||
type analysis struct {
|
||||
sshService analysisServices.Analysis
|
||||
|
||||
logger log.Logger
|
||||
notify notifications.Notifications
|
||||
}
|
||||
|
||||
func NewAnalysis(config *config.Config, logger log.Logger, notify notifications.Notifications) Analysis {
|
||||
return &analysis{
|
||||
sshService: analysisServices.NewSSH(config, logger, notify),
|
||||
logger: logger,
|
||||
notify: notify,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *analysis) SSH(entry *analysisServices.Entry) error {
|
||||
return a.sshService.Process(entry)
|
||||
}
|
||||
22
internal/daemon/analyzer/log/analysis/analysis.go
Normal file
22
internal/daemon/analyzer/log/analysis/analysis.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package analysis
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Analysis interface {
|
||||
Process(entry *Entry) error
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
Message string
|
||||
Unit string
|
||||
PID string
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
type EmptyAnalysis struct{}
|
||||
|
||||
func (empty *EmptyAnalysis) Process(entry *Entry) error {
|
||||
return nil
|
||||
}
|
||||
87
internal/daemon/analyzer/log/analysis/ssh.go
Normal file
87
internal/daemon/analyzer/log/analysis/ssh.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package analysis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/notifications"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
)
|
||||
|
||||
type ssh struct {
|
||||
login sshLogin
|
||||
|
||||
logger log.Logger
|
||||
notify notifications.Notifications
|
||||
}
|
||||
|
||||
type sshLogin struct {
|
||||
enabled bool
|
||||
notify bool
|
||||
}
|
||||
|
||||
type sshProcessReturn struct {
|
||||
found bool
|
||||
subject string
|
||||
body string
|
||||
}
|
||||
|
||||
func NewSSH(config *config.Config, logger log.Logger, notify notifications.Notifications) Analysis {
|
||||
if !config.Login.Enabled || !config.Login.SSH.Enabled {
|
||||
return &EmptyAnalysis{}
|
||||
}
|
||||
|
||||
return &ssh{
|
||||
login: sshLogin{
|
||||
enabled: config.Login.Enabled && config.Login.SSH.Enabled,
|
||||
notify: config.Login.Notify && config.Login.SSH.Notify,
|
||||
},
|
||||
|
||||
logger: logger,
|
||||
notify: notify,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ssh) Process(entry *Entry) error {
|
||||
if s.login.enabled {
|
||||
result, err := s.login.process(entry)
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("Failed to process ssh login: %s", err))
|
||||
} else if result.found {
|
||||
if s.login.notify {
|
||||
s.notify.SendAsync(notifications.Message{Subject: result.subject, Body: result.body})
|
||||
}
|
||||
s.logger.Info(fmt.Sprintf("SSH login detected: %s", entry.Message))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *sshLogin) process(entry *Entry) (sshProcessReturn, error) {
|
||||
re := regexp.MustCompile(`^Accepted (\S+) for (\S+) from (\S+) port \S+`)
|
||||
matches := re.FindStringSubmatch(entry.Message)
|
||||
|
||||
if matches != nil {
|
||||
user := matches[2]
|
||||
ip := matches[3]
|
||||
|
||||
return sshProcessReturn{
|
||||
found: true,
|
||||
subject: i18n.Lang.T("alert.login.subject", map[string]any{
|
||||
"User": user,
|
||||
"IP": ip,
|
||||
}),
|
||||
body: i18n.Lang.T("alert.login.body", map[string]any{
|
||||
"User": user,
|
||||
"IP": ip,
|
||||
"Log": entry.Message,
|
||||
"Time": entry.Time,
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return sshProcessReturn{found: false}, nil
|
||||
}
|
||||
147
internal/daemon/analyzer/log/systemd.go
Normal file
147
internal/daemon/analyzer/log/systemd.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
analysisServices "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
)
|
||||
|
||||
type Systemd interface {
|
||||
Run(ctx context.Context, logChan chan<- analysisServices.Entry)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type systemd struct {
|
||||
path string
|
||||
units []string
|
||||
logger log.Logger
|
||||
|
||||
cmd *exec.Cmd
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
type journalRawEntry struct {
|
||||
Message string `json:"MESSAGE"`
|
||||
Unit string `json:"_SYSTEMD_UNIT"`
|
||||
PID string `json:"_PID"`
|
||||
SourceTimestamp string `json:"_SOURCE_REALTIME_TIMESTAMP"`
|
||||
RealtimeTimestamp string `json:"__REALTIME_TIMESTAMP"`
|
||||
}
|
||||
|
||||
func NewSystemd(path string, units []string, logger log.Logger) Systemd {
|
||||
return &systemd{
|
||||
path: path,
|
||||
units: units,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *systemd) Run(ctx context.Context, logChan chan<- analysisServices.Entry) {
|
||||
if len(s.units) == 0 {
|
||||
s.logger.Debug("No units specified for journalctl")
|
||||
return
|
||||
}
|
||||
|
||||
args := []string{"-f", "-n", "0", "-o", "json"}
|
||||
for _, unit := range s.units {
|
||||
args = append(args, "-u", unit)
|
||||
}
|
||||
|
||||
s.logger.Debug("Journalctl started")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
if err := s.watch(ctx, args, logChan); err != nil {
|
||||
s.logger.Error(fmt.Sprintf("Journalctl exited with error: %v", err))
|
||||
}
|
||||
|
||||
// Pause before restarting to avoid CPU load during persistent errors
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
s.logger.Warn("Journalctl connection lost. Restarting in 5s...")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *systemd) watch(ctx context.Context, args []string, logChan chan<- analysisServices.Entry) error {
|
||||
cmd := exec.CommandContext(ctx, s.path, args...)
|
||||
|
||||
s.mu.Lock()
|
||||
s.cmd = cmd
|
||||
s.mu.Unlock()
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("stdout pipe error: %w", err)
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("start error: %w", err)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(stdout)
|
||||
for {
|
||||
var raw journalRawEntry
|
||||
if err := decoder.Decode(&raw); err != nil {
|
||||
if err == io.EOF {
|
||||
break // The process terminated normally or was killed.
|
||||
}
|
||||
return fmt.Errorf("decode error: %w", err)
|
||||
}
|
||||
|
||||
tsStr := raw.SourceTimestamp
|
||||
if tsStr == "" {
|
||||
tsStr = raw.RealtimeTimestamp
|
||||
}
|
||||
|
||||
var entryTime time.Time
|
||||
if usec, err := strconv.ParseInt(tsStr, 10, 64); err == nil {
|
||||
entryTime = time.Unix(0, usec*int64(time.Microsecond))
|
||||
} else {
|
||||
entryTime = time.Now()
|
||||
}
|
||||
|
||||
logChan <- analysisServices.Entry{
|
||||
Message: raw.Message,
|
||||
Unit: raw.Unit,
|
||||
PID: raw.PID,
|
||||
Time: entryTime,
|
||||
}
|
||||
}
|
||||
|
||||
return cmd.Wait()
|
||||
}
|
||||
|
||||
func (s *systemd) Close() error {
|
||||
if s.units == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.cmd != nil && s.cmd.Process != nil {
|
||||
s.logger.Debug("Stopping journalctl")
|
||||
|
||||
// Force journalctl to quit on shutdown
|
||||
return s.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
s.logger.Debug("Journalctl stopped")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/notifications"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/pidfile"
|
||||
@@ -23,6 +24,7 @@ type daemon struct {
|
||||
logger log.Logger
|
||||
firewall firewall.API
|
||||
notifications notifications.Notifications
|
||||
analyzer analyzer.Analyzer
|
||||
|
||||
stopCh chan struct{}
|
||||
}
|
||||
@@ -59,6 +61,11 @@ func (d *daemon) Run(ctx context.Context, isTesting bool, testingInterval uint16
|
||||
_ = d.notifications.Close()
|
||||
}()
|
||||
|
||||
d.analyzer.Run(ctx)
|
||||
defer func() {
|
||||
_ = d.analyzer.Close()
|
||||
}()
|
||||
|
||||
go d.socket.Run(ctx, d.socketCommand)
|
||||
d.runWorker(ctx, isTesting, testingInterval)
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package daemon
|
||||
|
||||
import "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall"
|
||||
import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall"
|
||||
)
|
||||
|
||||
type DaemonOptions struct {
|
||||
PathPidFile string
|
||||
PathSocketFile string
|
||||
PathNftables string
|
||||
ConfigFirewall firewall.Config
|
||||
ConfigAnalyzer config.Config
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package daemon
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer"
|
||||
firewall2 "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/notifications"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/pidfile"
|
||||
@@ -27,11 +28,14 @@ func NewDaemon(opts DaemonOptions, logger log.Logger, notifications notification
|
||||
|
||||
firewall, err := firewall2.New(opts.PathNftables, logger, opts.ConfigFirewall)
|
||||
|
||||
analyzerService := analyzer.New(opts.ConfigAnalyzer, logger, notifications)
|
||||
|
||||
return &daemon{
|
||||
pidFile: pidFile,
|
||||
socket: sock,
|
||||
logger: logger,
|
||||
firewall: firewall,
|
||||
notifications: notifications,
|
||||
analyzer: analyzerService,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -25,5 +25,8 @@
|
||||
"daemon stopped": "Daemon stopped",
|
||||
"daemon stop failed": "Daemon stop failed",
|
||||
"daemon is not running": "Daemon is not running",
|
||||
"daemon is not reopening logger": "The daemon did not reopen the log"
|
||||
}
|
||||
"daemon is not reopening logger": "The daemon did not reopen the log",
|
||||
|
||||
"alert.login.subject": "SSH login alert for user {{.User}} from {{.IP}}",
|
||||
"alert.login.body": "Logged into the OS via ssh:\n Time: {{.Time}}\n IP: {{.IP}}\n User: {{.User}}\n Log: {{.Log}}"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,8 @@
|
||||
"daemon stopped": "Жын тоқтатылды",
|
||||
"daemon stop failed": "Жынды тоқтату сәтсіз аяқталды",
|
||||
"daemon is not running": "Демон жұмыс істемейді",
|
||||
"daemon is not reopening logger": "Жын журналды қайта ашпады"
|
||||
"daemon is not reopening logger": "Жын журналды қайта ашпады",
|
||||
|
||||
"alert.login.subject": "{{.IP}} IP мекенжайынан {{.User}} пайдаланушысына арналған SSH кіру хабарламасы",
|
||||
"alert.login.body": "ОС-қа ssh арқылы кірді:\n Уақыт: {{.Time}}\n IP: {{.IP}}\n Пайдаланушы: {{.User}}\n Лог: {{.Log}}"
|
||||
}
|
||||
@@ -25,5 +25,8 @@
|
||||
"daemon stopped": "Демон остановлен",
|
||||
"daemon stop failed": "Остановка демона не удалась",
|
||||
"daemon is not running": "Демон не запущен",
|
||||
"daemon is not reopening logger": "Демон не открыл журнал повторно"
|
||||
"daemon is not reopening logger": "Демон не открыл журнал повторно",
|
||||
|
||||
"alert.login.subject": "SSH-сообщение о входе пользователя {{.User}} с IP-адреса {{.IP}}",
|
||||
"alert.login.body": "Вошли в ОС через ssh:\n Время: {{.Time}}\n IP: {{.IP}}\n Пользователь: {{.User}}\n Лог: {{.Log}}"
|
||||
}
|
||||
45
internal/setting/analyzer/analyzer.go
Normal file
45
internal/setting/analyzer/analyzer.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Setting struct {
|
||||
Login Login
|
||||
}
|
||||
|
||||
func InitSetting(path string) (Setting, error) {
|
||||
if err := validate.IsTomlFile(path, "otherSettingsPath.analyzer"); err != nil {
|
||||
return Setting{}, err
|
||||
}
|
||||
|
||||
setting := settingDefault()
|
||||
|
||||
v := viper.New()
|
||||
v.SetConfigType("toml")
|
||||
v.SetConfigFile(path)
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return Setting{}, err
|
||||
}
|
||||
if err := v.Unmarshal(&setting); err != nil {
|
||||
return Setting{}, err
|
||||
}
|
||||
|
||||
return setting, nil
|
||||
}
|
||||
|
||||
func settingDefault() Setting {
|
||||
return Setting{
|
||||
Login: defaultLogin(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s Setting) Validate() error {
|
||||
if err := s.Login.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
21
internal/setting/analyzer/login.go
Normal file
21
internal/setting/analyzer/login.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package analyzer
|
||||
|
||||
type Login struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Notify bool `mapstructure:"notify"`
|
||||
SSHEnable bool `mapstructure:"ssh_enable"`
|
||||
SSHNotify bool `mapstructure:"ssh_notify"`
|
||||
}
|
||||
|
||||
func defaultLogin() Login {
|
||||
return Login{
|
||||
Enabled: true,
|
||||
Notify: true,
|
||||
SSHEnable: true,
|
||||
SSHNotify: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (l Login) Validate() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package setting
|
||||
|
||||
type binaryLocations struct {
|
||||
Nftables string `mapstructure:"nftables"`
|
||||
Nftables string `mapstructure:"nftables"`
|
||||
Journalctl string `mapstructure:"journalctl"`
|
||||
}
|
||||
|
||||
func binaryLocationsDefault() *binaryLocations {
|
||||
return &binaryLocations{
|
||||
Nftables: "/usr/sbin/nft",
|
||||
Nftables: "/usr/sbin/nft",
|
||||
Journalctl: "/bin/journalctl",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/notifications"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
|
||||
analyzerSetting "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/analyzer"
|
||||
firewallSetting "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/firewall"
|
||||
notificationsSetting "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/notifications"
|
||||
"github.com/wneessen/go-mail"
|
||||
@@ -11,12 +16,14 @@ import (
|
||||
type otherSettingsPath struct {
|
||||
Firewall string `mapstructure:"firewall"`
|
||||
Notifications string `mapstructure:"notifications"`
|
||||
Analyzer string `mapstructure:"analyzer"`
|
||||
}
|
||||
|
||||
func otherSettingsPathDefault() *otherSettingsPath {
|
||||
return &otherSettingsPath{
|
||||
Firewall: "/etc/kor-elf-shield/firewall.toml",
|
||||
Notifications: "/etc/kor-elf-shield/notifications.toml",
|
||||
Analyzer: "/etc/kor-elf-shield/analyzer.toml",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,3 +124,38 @@ func (o *otherSettingsPath) ToNotificationsConfig() (notifications.Config, error
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *otherSettingsPath) ToAnalyzerConfig(binaryLocations *binaryLocations) (config.Config, error) {
|
||||
if binaryLocations.Journalctl == "" {
|
||||
return config.Config{}, errors.New(i18n.Lang.T("parameter is not specified", map[string]any{
|
||||
"Parameter": "binaryLocations.journalctl",
|
||||
}))
|
||||
}
|
||||
|
||||
setting, err := analyzerSetting.InitSetting(o.Analyzer)
|
||||
if err != nil {
|
||||
return config.Config{}, err
|
||||
}
|
||||
|
||||
if err := setting.Validate(); err != nil {
|
||||
return config.Config{}, err
|
||||
}
|
||||
|
||||
binPath := config.BinPath{
|
||||
Journalctl: binaryLocations.Journalctl,
|
||||
}
|
||||
|
||||
login := config.Login{
|
||||
Enabled: setting.Login.Enabled,
|
||||
Notify: setting.Login.Notify,
|
||||
SSH: config.LoginSSH{
|
||||
Enabled: setting.Login.SSHEnable,
|
||||
Notify: setting.Login.SSHNotify,
|
||||
},
|
||||
}
|
||||
|
||||
return config.Config{
|
||||
BinPath: binPath,
|
||||
Login: login,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -61,11 +61,17 @@ func (s setting) ToDaemonOptions() (daemon.DaemonOptions, error) {
|
||||
return daemon.DaemonOptions{}, err
|
||||
}
|
||||
|
||||
analyzerConfig, err := s.OtherSettingsPath.ToAnalyzerConfig(s.BinaryLocations)
|
||||
if err != nil {
|
||||
return daemon.DaemonOptions{}, err
|
||||
}
|
||||
|
||||
return daemon.DaemonOptions{
|
||||
PathPidFile: s.PidFile,
|
||||
PathSocketFile: s.SocketFile,
|
||||
PathNftables: s.BinaryLocations.Nftables,
|
||||
ConfigFirewall: firewallConfig,
|
||||
ConfigAnalyzer: analyzerConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user