Compare commits
19 Commits
b04016c596
...
v0.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a0cf7bd8a | |||
|
b938b73cfd
|
|||
|
ce031be060
|
|||
|
5e50bc179f
|
|||
|
279f58b644
|
|||
|
26365a519b
|
|||
|
d1f307d2ad
|
|||
|
ccf228242d
|
|||
|
5e12b1f6ab
|
|||
|
67abcc0ef2
|
|||
|
5ad40cdf9b
|
|||
|
374abcea80
|
|||
| 4748630b04 | |||
|
a75df70922
|
|||
|
a84f1ccde6
|
|||
|
0d13f851dd
|
|||
| bbaf0304c3 | |||
| 69157c90cb | |||
| e76d2ae398 |
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,4 +1,28 @@
|
||||
## 0.4.0 (soon)
|
||||
## 0.5.0 (17.1.2026)
|
||||
***
|
||||
#### Русский
|
||||
* В настройках analyzer.toml добавил параметры local_enable и local_notify.
|
||||
* local_enable = Включает отслеживание локальных авторизаций (TTY, физический доступ). По умолчанию включён.
|
||||
* local_notify = Включает уведомления о локальных авторизациях. По умолчанию включён.
|
||||
* В настройках analyzer.toml добавил параметры su_enable и su_notify.
|
||||
* su_enable = Включает отслеживание авторизаций через su. По умолчанию включён.
|
||||
* su_notify = Включает уведомления об авторизациях через su. По умолчанию включён.
|
||||
* В настройках analyzer.toml добавил параметры sudo_enable и sudo_notify.
|
||||
* sudo_enable = Включает отслеживание авторизаций через sudo. По умолчанию выключен.
|
||||
* sudo_notify = Включает уведомления об авторизациях через sudo. По умолчанию включён.
|
||||
***
|
||||
#### English
|
||||
* Added local_enable and local_notify parameters to analyzer.toml settings.
|
||||
* local_enable = Enables tracking of local logins (TTY, physical access). Enabled by default.
|
||||
* local_notify = Enables notifications about local logins. Enabled by default.
|
||||
* Added su_enable and su_notify parameters to analyzer.toml settings.
|
||||
* su_enable = Enables tracking of logins via su. Enabled by default.
|
||||
* su_notify = Enables notifications about logins via su. Enabled by default.
|
||||
* Added sudo_enable and sudo_notify parameters to analyzer.toml settings.
|
||||
* sudo_enable = Enables tracking of logins via sudo. Off by default.
|
||||
* sudo_notify = Enables notifications about logins via sudo. Enabled by default.
|
||||
***
|
||||
## 0.4.0 (11.1.2026)
|
||||
***
|
||||
#### Русский
|
||||
* Удалён параметр options.docker_support из файла firewall.toml. Настройки от Docker перенесены в файл docker.toml.
|
||||
@@ -8,6 +32,7 @@
|
||||
* Исправлена ошибка:
|
||||
* Настройка binaryLocations.docker не работала.
|
||||
* Программа аварийно завершалась после остановки Docker'а.
|
||||
* Указанные в настройках IP-адреса не блокировались во время перенаправления в контейнер Docker.
|
||||
***
|
||||
#### English
|
||||
* Removed the options.docker_support parameter from firewall.toml. Docker settings have been moved to the docker.toml file.
|
||||
@@ -17,6 +42,7 @@
|
||||
* Fixed error:
|
||||
* The binaryLocations.docker setting did not work.
|
||||
* The program crashed after Docker was stopped.
|
||||
* The IP addresses specified in the settings were not blocked during redirection to the Docker container.
|
||||
***
|
||||
## 0.3.0 (4.1.2026)
|
||||
***
|
||||
|
||||
@@ -45,3 +45,66 @@ ssh_enable = true
|
||||
# Default: true
|
||||
###
|
||||
ssh_notify = true
|
||||
|
||||
###
|
||||
# Включает отслеживание локальных авторизаций (TTY, физический доступ).
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables tracking of local authorizations (TTY, physical access).
|
||||
# Default: true
|
||||
###
|
||||
local_enable = true
|
||||
|
||||
###
|
||||
# Включает уведомления о локальных авторизациях.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables local authorization notifications.
|
||||
# Default: true
|
||||
###
|
||||
local_notify = true
|
||||
|
||||
###
|
||||
# Включает отслеживание, если кто-либо использует команду `su` для доступа к другой учетной записи.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables tracking if someone uses the `su` command to access another account.
|
||||
# Default: true
|
||||
###
|
||||
su_enable = true
|
||||
|
||||
###
|
||||
# Включает уведомления, если кто-либо использует команду `su` для доступа к другой учетной записи.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables notifications if someone uses the `su` command to access another account.
|
||||
# Default: true
|
||||
###
|
||||
su_notify = true
|
||||
|
||||
###
|
||||
# Включает отслеживание, если кто-либо использует команду `sudo` для доступа к другой учетной записи.
|
||||
#
|
||||
# ПРИМЕЧАНИЕ: Эта опция может стать обременительной, если команда sudo широко используется
|
||||
# для получения root-доступа администраторами или панелями управления.
|
||||
#
|
||||
# По умолчанию: false
|
||||
# ***
|
||||
# Enables tracking if someone uses the `sudo` command to access another account.
|
||||
#
|
||||
# NOTE: This option could become onerous if sudo is used extensively for root
|
||||
# access by administrators or control panels.
|
||||
#
|
||||
# Default: false
|
||||
###
|
||||
sudo_enable = false
|
||||
|
||||
###
|
||||
# Включает уведомления, если кто-либо использует команду `sudo` для доступа к другой учетной записи.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables notifications if someone uses the `sudo` command to access another account.
|
||||
# Default: true
|
||||
###
|
||||
sudo_notify = true
|
||||
|
||||
|
||||
@@ -28,8 +28,23 @@ type analyzer struct {
|
||||
|
||||
func New(config config2.Config, logger log.Logger, notify notifications.Notifications) Analyzer {
|
||||
var units []string
|
||||
if config.Login.Enabled && config.Login.SSH.Enabled {
|
||||
units = append(units, "ssh")
|
||||
|
||||
if config.Login.Enabled {
|
||||
if config.Login.SSH.Enabled {
|
||||
units = append(units, "_SYSTEMD_UNIT=ssh.service")
|
||||
}
|
||||
|
||||
if config.Login.Local.Enabled {
|
||||
units = append(units, "SYSLOG_IDENTIFIER=login")
|
||||
}
|
||||
|
||||
if config.Login.Su.Enabled {
|
||||
units = append(units, "SYSLOG_IDENTIFIER=su")
|
||||
}
|
||||
|
||||
if config.Login.Sudo.Enabled {
|
||||
units = append(units, "SYSLOG_IDENTIFIER=sudo")
|
||||
}
|
||||
}
|
||||
|
||||
systemdService := analyzerLog.NewSystemd(config.BinPath.Journalctl, units, logger)
|
||||
@@ -63,14 +78,26 @@ func (a *analyzer) processLogs(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
a.logger.Debug(fmt.Sprintf("Received log entry: %s", entry))
|
||||
switch entry.Unit {
|
||||
case "ssh.service":
|
||||
|
||||
switch {
|
||||
case entry.Unit == "ssh.service":
|
||||
if err := a.analysis.SSH(&entry); err != nil {
|
||||
a.logger.Error(fmt.Sprintf("Failed to analyze SSH logs: %s", err))
|
||||
}
|
||||
break
|
||||
case entry.SyslogIdentifier == "login":
|
||||
if err := a.analysis.Locale(&entry); err != nil {
|
||||
a.logger.Error(fmt.Sprintf("Failed to analyze locale logs: %s", err))
|
||||
}
|
||||
case entry.SyslogIdentifier == "sudo":
|
||||
if err := a.analysis.Sudo(&entry); err != nil {
|
||||
a.logger.Error(fmt.Sprintf("Failed to analyze sudo logs: %s", err))
|
||||
}
|
||||
case entry.SyslogIdentifier == "su":
|
||||
if err := a.analysis.Su(&entry); err != nil {
|
||||
a.logger.Error(fmt.Sprintf("Failed to analyze su logs: %s", err))
|
||||
}
|
||||
default:
|
||||
a.logger.Warn(fmt.Sprintf("Unknown unit: %s", entry.Unit))
|
||||
a.logger.Debug(fmt.Sprintf("Unknown unit or SyslogIdentifier: %s", entry.Unit))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,27 @@ type Login struct {
|
||||
Enabled bool
|
||||
Notify bool
|
||||
SSH LoginSSH
|
||||
Local LoginLocal
|
||||
Su LoginSu
|
||||
Sudo LoginSudo
|
||||
}
|
||||
|
||||
type LoginSSH struct {
|
||||
Enabled bool
|
||||
Notify bool
|
||||
}
|
||||
|
||||
type LoginLocal struct {
|
||||
Enabled bool
|
||||
Notify bool
|
||||
}
|
||||
|
||||
type LoginSu struct {
|
||||
Enabled bool
|
||||
Notify bool
|
||||
}
|
||||
|
||||
type LoginSudo struct {
|
||||
Enabled bool
|
||||
Notify bool
|
||||
}
|
||||
|
||||
@@ -9,10 +9,16 @@ import (
|
||||
|
||||
type Analysis interface {
|
||||
SSH(entry *analysisServices.Entry) error
|
||||
Locale(entry *analysisServices.Entry) error
|
||||
Su(entry *analysisServices.Entry) error
|
||||
Sudo(entry *analysisServices.Entry) error
|
||||
}
|
||||
|
||||
type analysis struct {
|
||||
sshService analysisServices.Analysis
|
||||
sshService analysisServices.Analysis
|
||||
localeService analysisServices.Analysis
|
||||
suService analysisServices.Analysis
|
||||
sudoService analysisServices.Analysis
|
||||
|
||||
logger log.Logger
|
||||
notify notifications.Notifications
|
||||
@@ -20,12 +26,27 @@ type analysis struct {
|
||||
|
||||
func NewAnalysis(config *config.Config, logger log.Logger, notify notifications.Notifications) Analysis {
|
||||
return &analysis{
|
||||
sshService: analysisServices.NewSSH(config, logger, notify),
|
||||
logger: logger,
|
||||
notify: notify,
|
||||
sshService: analysisServices.NewSSH(config, logger, notify),
|
||||
localeService: analysisServices.NewLocale(config, logger, notify),
|
||||
suService: analysisServices.NewSu(config, logger, notify),
|
||||
sudoService: analysisServices.NewSudo(config, logger, notify),
|
||||
logger: logger,
|
||||
notify: notify,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *analysis) SSH(entry *analysisServices.Entry) error {
|
||||
return a.sshService.Process(entry)
|
||||
}
|
||||
|
||||
func (a *analysis) Locale(entry *analysisServices.Entry) error {
|
||||
return a.localeService.Process(entry)
|
||||
}
|
||||
|
||||
func (a *analysis) Su(entry *analysisServices.Entry) error {
|
||||
return a.suService.Process(entry)
|
||||
}
|
||||
|
||||
func (a *analysis) Sudo(entry *analysisServices.Entry) error {
|
||||
return a.sudoService.Process(entry)
|
||||
}
|
||||
|
||||
@@ -9,10 +9,17 @@ type Analysis interface {
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
Message string
|
||||
Unit string
|
||||
PID string
|
||||
Time time.Time
|
||||
Message string
|
||||
Unit string
|
||||
PID string
|
||||
SyslogIdentifier string
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
type processReturn struct {
|
||||
found bool
|
||||
subject string
|
||||
body string
|
||||
}
|
||||
|
||||
type EmptyAnalysis struct{}
|
||||
|
||||
78
internal/daemon/analyzer/log/analysis/locale.go
Normal file
78
internal/daemon/analyzer/log/analysis/locale.go
Normal file
@@ -0,0 +1,78 @@
|
||||
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 locale struct {
|
||||
login localeLogin
|
||||
|
||||
logger log.Logger
|
||||
notify notifications.Notifications
|
||||
}
|
||||
|
||||
type localeLogin struct {
|
||||
enabled bool
|
||||
notify bool
|
||||
}
|
||||
|
||||
func NewLocale(config *config.Config, logger log.Logger, notify notifications.Notifications) Analysis {
|
||||
if !config.Login.Enabled || !config.Login.Local.Enabled {
|
||||
return &EmptyAnalysis{}
|
||||
}
|
||||
|
||||
return &locale{
|
||||
login: localeLogin{
|
||||
enabled: config.Login.Enabled && config.Login.SSH.Enabled,
|
||||
notify: config.Login.Notify && config.Login.SSH.Notify,
|
||||
},
|
||||
|
||||
logger: logger,
|
||||
notify: notify,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *locale) Process(entry *Entry) error {
|
||||
if l.login.enabled {
|
||||
result, err := l.login.process(entry)
|
||||
if err != nil {
|
||||
l.logger.Error(fmt.Sprintf("Failed to process TTY login: %s", err))
|
||||
} else if result.found {
|
||||
if l.login.notify {
|
||||
l.notify.SendAsync(notifications.Message{Subject: result.subject, Body: result.body})
|
||||
}
|
||||
l.logger.Info(fmt.Sprintf("TTY login detected: %s", entry.Message))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *localeLogin) process(entry *Entry) (processReturn, error) {
|
||||
re := regexp.MustCompile(`^pam_unix\(login:session\): session opened for user (\S+)\(\S+\) by \S+`)
|
||||
matches := re.FindStringSubmatch(entry.Message)
|
||||
|
||||
if matches != nil {
|
||||
user := matches[1]
|
||||
|
||||
return processReturn{
|
||||
found: true,
|
||||
subject: i18n.Lang.T("alert.login.locale.subject", map[string]any{
|
||||
"User": user,
|
||||
}),
|
||||
body: i18n.Lang.T("alert.login.locale.body", map[string]any{
|
||||
"User": user,
|
||||
"Log": entry.Message,
|
||||
"Time": entry.Time,
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return processReturn{found: false}, nil
|
||||
}
|
||||
@@ -22,12 +22,6 @@ type sshLogin struct {
|
||||
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{}
|
||||
@@ -60,7 +54,7 @@ func (s *ssh) Process(entry *Entry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *sshLogin) process(entry *Entry) (sshProcessReturn, error) {
|
||||
func (l *sshLogin) process(entry *Entry) (processReturn, error) {
|
||||
re := regexp.MustCompile(`^Accepted (\S+) for (\S+) from (\S+) port \S+`)
|
||||
matches := re.FindStringSubmatch(entry.Message)
|
||||
|
||||
@@ -68,13 +62,13 @@ func (l *sshLogin) process(entry *Entry) (sshProcessReturn, error) {
|
||||
user := matches[2]
|
||||
ip := matches[3]
|
||||
|
||||
return sshProcessReturn{
|
||||
return processReturn{
|
||||
found: true,
|
||||
subject: i18n.Lang.T("alert.login.subject", map[string]any{
|
||||
subject: i18n.Lang.T("alert.login.ssh.subject", map[string]any{
|
||||
"User": user,
|
||||
"IP": ip,
|
||||
}),
|
||||
body: i18n.Lang.T("alert.login.body", map[string]any{
|
||||
body: i18n.Lang.T("alert.login.ssh.body", map[string]any{
|
||||
"User": user,
|
||||
"IP": ip,
|
||||
"Log": entry.Message,
|
||||
@@ -83,5 +77,5 @@ func (l *sshLogin) process(entry *Entry) (sshProcessReturn, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
return sshProcessReturn{found: false}, nil
|
||||
return processReturn{found: false}, nil
|
||||
}
|
||||
|
||||
81
internal/daemon/analyzer/log/analysis/su.go
Normal file
81
internal/daemon/analyzer/log/analysis/su.go
Normal file
@@ -0,0 +1,81 @@
|
||||
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 su struct {
|
||||
login suLogin
|
||||
|
||||
logger log.Logger
|
||||
notify notifications.Notifications
|
||||
}
|
||||
|
||||
type suLogin struct {
|
||||
enabled bool
|
||||
notify bool
|
||||
}
|
||||
|
||||
func NewSu(config *config.Config, logger log.Logger, notify notifications.Notifications) Analysis {
|
||||
if !config.Login.Enabled || !config.Login.Su.Enabled {
|
||||
return &EmptyAnalysis{}
|
||||
}
|
||||
|
||||
return &su{
|
||||
login: suLogin{
|
||||
enabled: config.Login.Enabled && config.Login.Su.Enabled,
|
||||
notify: config.Login.Notify && config.Login.Su.Notify,
|
||||
},
|
||||
|
||||
logger: logger,
|
||||
notify: notify,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *su) Process(entry *Entry) error {
|
||||
if l.login.enabled {
|
||||
result, err := l.login.process(entry)
|
||||
if err != nil {
|
||||
l.logger.Error(fmt.Sprintf("Failed to process Su login: %s", err))
|
||||
} else if result.found {
|
||||
if l.login.notify {
|
||||
l.notify.SendAsync(notifications.Message{Subject: result.subject, Body: result.body})
|
||||
}
|
||||
l.logger.Info(fmt.Sprintf("Su login detected: %s", entry.Message))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *suLogin) process(entry *Entry) (processReturn, error) {
|
||||
re := regexp.MustCompile(`^pam_unix\(su:session\): session opened for user (\S+)\(\S+\) by (\S+)\(\S+\)`)
|
||||
matches := re.FindStringSubmatch(entry.Message)
|
||||
|
||||
if matches != nil {
|
||||
user := matches[1]
|
||||
byUser := matches[2]
|
||||
|
||||
return processReturn{
|
||||
found: true,
|
||||
subject: i18n.Lang.T("alert.login.su.subject", map[string]any{
|
||||
"User": user,
|
||||
"ByUser": byUser,
|
||||
}),
|
||||
body: i18n.Lang.T("alert.login.su.body", map[string]any{
|
||||
"User": user,
|
||||
"ByUser": byUser,
|
||||
"Log": entry.Message,
|
||||
"Time": entry.Time,
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return processReturn{found: false}, nil
|
||||
}
|
||||
81
internal/daemon/analyzer/log/analysis/sudo.go
Normal file
81
internal/daemon/analyzer/log/analysis/sudo.go
Normal file
@@ -0,0 +1,81 @@
|
||||
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 sudo struct {
|
||||
login sudoLogin
|
||||
|
||||
logger log.Logger
|
||||
notify notifications.Notifications
|
||||
}
|
||||
|
||||
type sudoLogin struct {
|
||||
enabled bool
|
||||
notify bool
|
||||
}
|
||||
|
||||
func NewSudo(config *config.Config, logger log.Logger, notify notifications.Notifications) Analysis {
|
||||
if !config.Login.Enabled || !config.Login.Su.Enabled {
|
||||
return &EmptyAnalysis{}
|
||||
}
|
||||
|
||||
return &sudo{
|
||||
login: sudoLogin{
|
||||
enabled: config.Login.Enabled && config.Login.Sudo.Enabled,
|
||||
notify: config.Login.Notify && config.Login.Sudo.Notify,
|
||||
},
|
||||
|
||||
logger: logger,
|
||||
notify: notify,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sudo) 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 Sudo 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("Sudo login detected: %s", entry.Message))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sudoLogin) process(entry *Entry) (processReturn, error) {
|
||||
re := regexp.MustCompile(`^pam_unix\(sudo:session\): session opened for user (\S+)\(\S+\) by (\S+)\(\S+\)`)
|
||||
matches := re.FindStringSubmatch(entry.Message)
|
||||
|
||||
if matches != nil {
|
||||
user := matches[1]
|
||||
byUser := matches[2]
|
||||
|
||||
return processReturn{
|
||||
found: true,
|
||||
subject: i18n.Lang.T("alert.login.sudo.subject", map[string]any{
|
||||
"User": user,
|
||||
"ByUser": byUser,
|
||||
}),
|
||||
body: i18n.Lang.T("alert.login.sudo.body", map[string]any{
|
||||
"User": user,
|
||||
"ByUser": byUser,
|
||||
"Log": entry.Message,
|
||||
"Time": entry.Time,
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return processReturn{found: false}, nil
|
||||
}
|
||||
@@ -32,6 +32,7 @@ type journalRawEntry struct {
|
||||
Message string `json:"MESSAGE"`
|
||||
Unit string `json:"_SYSTEMD_UNIT"`
|
||||
PID string `json:"_PID"`
|
||||
SyslogIdentifier string `json:"SYSLOG_IDENTIFIER"`
|
||||
SourceTimestamp string `json:"_SOURCE_REALTIME_TIMESTAMP"`
|
||||
RealtimeTimestamp string `json:"__REALTIME_TIMESTAMP"`
|
||||
}
|
||||
@@ -74,8 +75,11 @@ func (s *systemd) Run(ctx context.Context, logChan chan<- analysisServices.Entry
|
||||
|
||||
func (s *systemd) watch(ctx context.Context, logChan chan<- analysisServices.Entry) error {
|
||||
args := []string{"-f", "-n", "0", "-o", "json"}
|
||||
for _, unit := range s.units {
|
||||
args = append(args, "-u", unit)
|
||||
for index, unit := range s.units {
|
||||
if index > 0 {
|
||||
args = append(args, "+")
|
||||
}
|
||||
args = append(args, unit)
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, s.path, args...)
|
||||
|
||||
@@ -115,10 +119,11 @@ func (s *systemd) watch(ctx context.Context, logChan chan<- analysisServices.Ent
|
||||
}
|
||||
|
||||
logChan <- analysisServices.Entry{
|
||||
Message: raw.Message,
|
||||
Unit: raw.Unit,
|
||||
PID: raw.PID,
|
||||
Time: entryTime,
|
||||
Message: raw.Message,
|
||||
Unit: raw.Unit,
|
||||
PID: raw.PID,
|
||||
SyslogIdentifier: raw.SyslogIdentifier,
|
||||
Time: entryTime,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ type Chains interface {
|
||||
NewLocalOutput() error
|
||||
LocalOutput() LocalOutput
|
||||
|
||||
NewLocalForward() error
|
||||
LocalForward() LocalForward
|
||||
|
||||
ClearRules() error
|
||||
|
||||
NewNoneChain(chain string) (Chain, error)
|
||||
@@ -39,8 +42,9 @@ type chains struct {
|
||||
forward Forward
|
||||
packetFilter PacketFilter
|
||||
|
||||
localInput LocalInput
|
||||
localOutput LocalOutput
|
||||
localInput LocalInput
|
||||
localOutput LocalOutput
|
||||
localForward LocalForward
|
||||
|
||||
family nftFamily.Type
|
||||
table string
|
||||
@@ -147,6 +151,19 @@ func (c *chains) LocalOutput() LocalOutput {
|
||||
return c.localOutput
|
||||
}
|
||||
|
||||
func (c *chains) NewLocalForward() error {
|
||||
localForward, err := newLocalForward(c.nft, c.family, c.table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.localForward = localForward
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chains) LocalForward() LocalForward {
|
||||
return c.localForward
|
||||
}
|
||||
|
||||
func (c *chains) ClearRules() error {
|
||||
return clearRules(c.nft, c.family, c.table)
|
||||
}
|
||||
|
||||
41
internal/daemon/firewall/chain/local_forward.go
Normal file
41
internal/daemon/firewall/chain/local_forward.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type LocalForward interface {
|
||||
AddRule(expr ...string) error
|
||||
AddRuleIn(AddRuleFunc func(expr ...string) error) error
|
||||
}
|
||||
|
||||
type localForward struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
func newLocalForward(nft nft.NFT, family family.Type, table string) (LocalForward, error) {
|
||||
chain := "local-forward"
|
||||
if err := nft.Chain().Add(family, table, chain, nftChain.TypeNone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &localForward{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *localForward) AddRule(expr ...string) error {
|
||||
return l.nft.Rule().Add(l.family, l.table, l.chain, expr...)
|
||||
}
|
||||
|
||||
func (l *localForward) AddRuleIn(AddRuleFunc func(expr ...string) error) error {
|
||||
return AddRuleFunc("iifname != \"lo\" counter jump " + l.chain)
|
||||
}
|
||||
@@ -8,6 +8,10 @@ func (f *firewall) reloadForward() error {
|
||||
}
|
||||
chain := f.chains.Forward()
|
||||
|
||||
if err := f.reloadForwardAddIPs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.config.Options.DockerSupport {
|
||||
if err := f.docker.NftChains().ForwardFilterJump(chain.AddRule); err != nil {
|
||||
return err
|
||||
@@ -23,3 +27,53 @@ func (f *firewall) reloadForward() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadForwardAddIPs() error {
|
||||
if err := f.chains.NewLocalForward(); err != nil {
|
||||
return err
|
||||
}
|
||||
chain := f.chains.LocalForward()
|
||||
if err := chain.AddRuleIn(f.chains.Forward().AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ipConfig := range f.config.IP4.InIPs {
|
||||
if ipConfig.Action != ActionDrop && ipConfig.Action != ActionReject {
|
||||
continue
|
||||
}
|
||||
if err := forwardAddIP(chain.AddRule, ipConfig, "ip"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.config.IP6.Enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ipConfig := range f.config.IP6.InIPs {
|
||||
if ipConfig.Action != ActionDrop && ipConfig.Action != ActionReject {
|
||||
continue
|
||||
}
|
||||
if err := forwardAddIP(chain.AddRule, ipConfig, "ip6"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func forwardAddIP(addRuleFunc func(expr ...string) error, config ConfigIP, ipMatch string) error {
|
||||
rule := ipMatch + " saddr " + config.IP + " iifname != \"lo\""
|
||||
|
||||
// There, during routing, the port changes and then the IP blocking rule will not work.
|
||||
//if !config.OnlyIP {
|
||||
// rule += " " + config.Protocol.String() + " dport " + strconv.Itoa(int(config.Port))
|
||||
//}
|
||||
|
||||
rule += " counter " + config.Action.String()
|
||||
if err := addRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,6 +27,15 @@
|
||||
"daemon is not running": "Daemon is not running",
|
||||
"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}}"
|
||||
"alert.login.ssh.subject": "SSH login alert for user {{.User}} from {{.IP}}",
|
||||
"alert.login.ssh.body": "Logged into the OS via ssh:\n Time: {{.Time}}\n IP: {{.IP}}\n User: {{.User}}\n Log: {{.Log}}",
|
||||
|
||||
"alert.login.locale.subject": "Login message for user {{.User}} (TTY)",
|
||||
"alert.login.locale.body": "Logged into the OS via TTY:\n Time: {{.Time}}\n User: {{.User}}\n Log: {{.Log}}",
|
||||
|
||||
"alert.login.su.subject": "User {{.ByUser}} has accessed user {{.User}} via su",
|
||||
"alert.login.su.body": "User {{.ByUser}} accessed user {{.User}} via su.\nTime: {{.Time}}\nLog: {{.Log}}",
|
||||
|
||||
"alert.login.sudo.subject": "User {{.ByUser}} has accessed user {{.User}} via sudo",
|
||||
"alert.login.sudo.body": "User {{.ByUser}} accessed user {{.User}} via sudo.\nTime: {{.Time}}\nLog: {{.Log}}"
|
||||
}
|
||||
|
||||
@@ -27,6 +27,15 @@
|
||||
"daemon is not running": "Демон жұмыс істемейді",
|
||||
"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}}"
|
||||
"alert.login.ssh.subject": "{{.IP}} IP мекенжайынан {{.User}} пайдаланушысына арналған SSH кіру хабарламасы",
|
||||
"alert.login.ssh.body": "ОС-қа ssh арқылы кірді:\n Уақыт: {{.Time}}\n IP: {{.IP}}\n Пайдаланушы: {{.User}}\n Лог: {{.Log}}",
|
||||
|
||||
"alert.login.locale.subject": "{{.User}} пайдаланушысына арналған кіру хабарламасы (TTY)",
|
||||
"alert.login.locale.body": "ОЖ-ға TTY арқылы кірдіңіз:\n Уақыт: {{.Time}}\n Пайдаланушы: {{.User}}\n Лог: {{.Log}}",
|
||||
|
||||
"alert.login.su.subject": "{{.ByUser}} пайдаланушысы {{.User}} пайдаланушысына su арқылы кіру мүмкіндігін алды",
|
||||
"alert.login.su.body": "{{.ByUser}} пайдаланушысы {{.User}} пайдаланушысына su арқылы кірді.\nУақыты: {{.Time}}\nЛог: {{.Log}}",
|
||||
|
||||
"alert.login.sudo.subject": "{{.ByUser}} пайдаланушысы {{.User}} пайдаланушысына sudo арқылы кіру мүмкіндігін алды",
|
||||
"alert.login.sudo.body": "{{.ByUser}} пайдаланушысы {{.User}} пайдаланушысына sudo арқылы кірді.\nУақыты: {{.Time}}\nЛог: {{.Log}}"
|
||||
}
|
||||
@@ -27,6 +27,15 @@
|
||||
"daemon is not running": "Демон не запущен",
|
||||
"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}}"
|
||||
"alert.login.ssh.subject": "SSH-сообщение о входе пользователя {{.User}} с IP-адреса {{.IP}}",
|
||||
"alert.login.ssh.body": "Вошли в ОС через ssh:\n Время: {{.Time}}\n IP: {{.IP}}\n Пользователь: {{.User}}\n Лог: {{.Log}}",
|
||||
|
||||
"alert.login.locale.subject": "Сообщение о входе пользователя {{.User}} (TTY)",
|
||||
"alert.login.locale.body": "Вошли в ОС через TTY:\n Время: {{.Time}}\n Пользователь: {{.User}}\n Лог: {{.Log}}",
|
||||
|
||||
"alert.login.su.subject": "Пользователь {{.ByUser}} получил доступ к пользователю {{.User}} через su",
|
||||
"alert.login.su.body": "Пользователь {{.ByUser}} получил доступ к пользователю {{.User}} через su.\nВремя: {{.Time}}\nЛог: {{.Log}}",
|
||||
|
||||
"alert.login.sudo.subject": "Пользователь {{.ByUser}} получил доступ к пользователю {{.User}} через sudo",
|
||||
"alert.login.sudo.body": "Пользователь {{.ByUser}} получил доступ к пользователю {{.User}} через sudo.\nВремя: {{.Time}}\nЛог: {{.Log}}"
|
||||
}
|
||||
@@ -1,18 +1,38 @@
|
||||
package analyzer
|
||||
|
||||
type Login struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Notify bool `mapstructure:"notify"`
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Notify bool `mapstructure:"notify"`
|
||||
|
||||
SSHEnable bool `mapstructure:"ssh_enable"`
|
||||
SSHNotify bool `mapstructure:"ssh_notify"`
|
||||
|
||||
LocalEnable bool `mapstructure:"local_enable"`
|
||||
LocalNotify bool `mapstructure:"local_notify"`
|
||||
|
||||
SuEnable bool `mapstructure:"su_enable"`
|
||||
SuNotify bool `mapstructure:"su_notify"`
|
||||
|
||||
SudoEnable bool `mapstructure:"sudo_enable"`
|
||||
SudoNotify bool `mapstructure:"sudo_notify"`
|
||||
}
|
||||
|
||||
func defaultLogin() Login {
|
||||
return Login{
|
||||
Enabled: true,
|
||||
Notify: true,
|
||||
Enabled: true,
|
||||
Notify: true,
|
||||
|
||||
SSHEnable: true,
|
||||
SSHNotify: true,
|
||||
|
||||
LocalEnable: true,
|
||||
LocalNotify: true,
|
||||
|
||||
SuEnable: true,
|
||||
SuNotify: true,
|
||||
|
||||
SudoEnable: false,
|
||||
SudoNotify: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,18 @@ func (o *otherSettingsPath) ToAnalyzerConfig(binaryLocations *binaryLocations) (
|
||||
Enabled: setting.Login.SSHEnable,
|
||||
Notify: setting.Login.SSHNotify,
|
||||
},
|
||||
Local: config.LoginLocal{
|
||||
Enabled: setting.Login.LocalEnable,
|
||||
Notify: setting.Login.LocalNotify,
|
||||
},
|
||||
Su: config.LoginSu{
|
||||
Enabled: setting.Login.SuEnable,
|
||||
Notify: setting.Login.SuNotify,
|
||||
},
|
||||
Sudo: config.LoginSudo{
|
||||
Enabled: setting.Login.SudoEnable,
|
||||
Notify: setting.Login.SudoNotify,
|
||||
},
|
||||
}
|
||||
|
||||
return config.Config{
|
||||
|
||||
Reference in New Issue
Block a user