zimbra-alarm/alarmaction.go

372 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//----------------------------------------------------------------------------
// Получение и обработка данных о пользовательских сессиях (авторизация)
// при соединении с почтовой системой. И блокирование нежелательных адресов.
// ---------------------------------------------------------------------------
// Версия 2 (ipset)
// ---------------------------------------------------------------------------
package main
import (
"log"
"strings"
"net/netip"
"time"
"context"
"fmt"
"os"
"io/ioutil"
"path/filepath"
// "github.com/coreos/go-iptables/iptables"
"github.com/gonetx/ipset"
"github.com/jackc/pgx/v5"
// "github.com/jackc/pgtype"
)
// проверяем наличие ipset системе
func CheckIPSet() {
if err := ipset.Check(); err != nil {
panic(err)
}
}
type Alarm struct {
Id int32
Mail_account_id int32
Remote_ip netip.Prefix
Country_code string
Last_time string
Last_check_timestamp time.Time
Action_timestamp time.Time
Alarm_type int
Alarm_action bool
Hostname string
}
// PIDFile stored the process id
type PIDFile struct {
path string
}
var PID_file_path = "/var/run/alarmaction.pid"
// Remove the pid file
// func (file PIDFile) PIDRemove() error {
// return os.Remove(file.path)
// }
func ProcessExit(exit_code int) {
if exit_code == 0 {
os.Remove(PID_file_path)
}
os.Exit(exit_code)
}
// Выборка записей по конкретному IP
// запросы разные в зависимости от типа тревоги
func PgSelectAlarmRemoteIP(alarm Alarm) pgx.Rows {
var query string
conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
// fmt.Println(*conn)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
ProcessExit(1)
}
switch alarm.Alarm_type {
case 4:
query = fmt.Sprintf("select id, remote_ip from alarm where alarm_type = %v and \"%v_action_timestamp\" is NULL", alarm.Alarm_type, alarm.Hostname)
}
log.Println(query)
rows, err_select := conn.Query(context.Background(), query)
if err_select != nil {
log.Println("Query alarms failed PgSelectAlarmRemoteIP:", err_select)
ProcessExit(1)
}
if len(rows.FieldDescriptions()) == 0 {
ProcessExit(1)
}
// log.Println("Selected an:",len(rows.FieldDescriptions()), "records")
defer conn.Close(context.Background())
return rows
}
// Проверка на соответствие адреса записи в filter
func PgSelectFilterIP(ip netip.Prefix) int32 {
var query string
var id int32
conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
// fmt.Println(*conn)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
ProcessExit(1)
}
// проверяем адрес на совпадение с фильтром (для исключения)
// select * from filter where remote_ip >>= inet '209.85.161.20/32'
query = fmt.Sprintf("select id from filter where remote_ip >>= inet '%v'", ip)
log.Println(query)
err_select := conn.QueryRow(context.Background(), query).Scan(&id)
if err_select != nil {
log.Println("Select filter for remote ip", ip, "error:", err)
if err_select.Error() == "no rows in result set" {
id = 0
} else {
ProcessExit(1)
}
}
return id
}
func PgUpdateAlarm(conn *pgx.Conn, alarm Alarm, id int32) int32 {
log.Println("Update alarm data", alarm)
t := time.Now().Format("2006-01-02 15:04:05")
query := fmt.Sprintf("update alarm set \"%v_action_timestamp\"='%v' where id=%v returning id", alarm.Hostname, t, id)
log.Println("Update alarm query:", query)
err := conn.QueryRow(context.Background(), query).Scan(&id)
if err != nil {
log.Printf("Update alarm data failed: %v\n", err)
}
return id
}
// func IpTablesCreateChain (sChain string) bool {
// ipt, err := iptables.New()
// if err != nil {
// log.Printf("Failed to new up an IPtables intance. ERROR: %v", err)
// ProcessExit(1)
// }
// var chain_exists bool
// chain_exists, err = ipt.ChainExists("filter", sChain)
// if err != nil {
// log.Printf("Failed to checking chain (%v). ERROR: %v", sChain , err)
// return false
// }
// if chain_exists {
// return true
// } else {
// err = ipt.NewChain("filter", sChain)
// if err != nil {
// log.Printf("Failed to creating chain (%v). ERROR: %v", sChain , err)
// return false
// }
// err = ipt.AppendUnique("filter", "INPUT", "-j", sChain)
// if err != nil {
// log.Printf("Failed to append chain. ERROR: %v", err)
// return false
// }
// }
//
// return true
//
// }
// func BlockIP(ip netip.Prefix, sChain string, dChain string) bool {
// // Some default chain names
//
// // Get a new iptables interface
// ipt, err := iptables.New()
// if err != nil {
// log.Printf("Failed to new up an IPtables intance. ERROR: %v", err)
// ProcessExit(1)
// }
// // Build out the ipstring(add /32 to the end)
// ipstr := fmt.Sprintf("%s", ip)
// res, _ := ipt.Exists("filter", sChain, "-s", ipstr, "-j", dChain)
// if !res {
// // Use the appendUnique method to put this in iptables, but only once
// err = ipt.AppendUnique("filter", sChain, "-s", ipstr, "-j", dChain)
// if err != nil {
// log.Printf("Failed to ban an ip(%v). ERROR: %v", ipstr , err)
// return false
// }
// } else {
// log.Printf("Failed to ban an ip(%v). ERROR: Address already banned", ipstr)
// }
//
// // Since we made it here, we won
// return true
// }
func BlockIP(ip netip.Prefix, sChain string, dChain string) bool {
// create test set even it's exist
// set, err := ipset.New("auto_blocked", ipset.HashIp, ipset.Exist(true), ipset.Timeout(time.Hour))
ip_address := ip.String()
set, err := ipset.New("auto-blocked-ipset", ipset.HashIp, ipset.Exist(true))
// output: test
if err != nil {
log.Printf("Failed to create new ipset rule. ERROR: %v", err)
// return false
} else {
log.Printf("New ipset rule %v was created", set.Name())
}
// _ = set.Flush()
// _ = set.Add("1.1.1.1", ipset.Timeout(time.Hour))
err = set.Add(ip_address)
if err != nil {
log.Printf("Failed to added new %v to set. ERROR: %v", ip_address, err)
return false
}
ok, err := set.Test(ip_address)
// output: true
log.Println(ok)
if err != nil {
log.Printf("Failed to added new %v to set. ERROR: %v", ip_address, err)
return false
}
// ok, _ = set.Test("1.1.1.2")
// output: false
// log.Println(ok)
// info, _ := set.List()
// output: &{test hash:ip 4 family inet hashsize 1024 maxelem 65536 timeout 3600 216 0 [1.1.1.1 timeout 3599]}
// log.Println(info)
// _ = set.Del("1.1.1.1")
// _ = set.Destroy()
return true
}
// just suit for linux
func processExists(pid string) bool {
if _, err := os.Stat(filepath.Join("/proc", pid)); err == nil {
return true
}
return false
}
func checkPIDFILEAlreadyExists(path string) error {
if pidByte, err := ioutil.ReadFile(path); err == nil {
pid := strings.TrimSpace(string(pidByte))
if processExists(pid) {
return fmt.Errorf("ensure the process:%s is not running pid file:%s", pid, path)
}
}
return nil
}
// NewPIDFile create the pid file
// path specified under production pidfile, file content pid
func NewPIDFile(path string) (*PIDFile, error) {
if err := checkPIDFILEAlreadyExists(path); err != nil {
return nil, err
}
if err := os.MkdirAll(filepath.Dir(path), os.FileMode(0755)); err != nil {
return nil, err
}
if err := ioutil.WriteFile(path, []byte(fmt.Sprintf("%d", os.Getpid())), 0644); err != nil {
return nil, err
}
return &PIDFile{path: path}, nil
}
func main() {
CheckIPSet()
// Создадим и откроем на запись файл для логов
var alarm_log_file string
if os.Getenv("ALARM_LOG_FILE") == "" {
alarm_log_file = "alarmaction.log"
} else {
alarm_log_file = os.Getenv("ALARM_LOG_FILE")
}
f, err := os.OpenFile(alarm_log_file, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
if err != nil {
log.Fatalf("error opening file: %v", err)
}
defer f.Close()
log.SetOutput(f)
// Создание PID файла
_, err = NewPIDFile(PID_file_path)
if err != nil {
log.Println("error to create the pid file failed:", err.Error())
ProcessExit(1)
}
// Проверка переменных окружения
if os.Getenv("PGHOST") == "" {
fmt.Println("Send error: make sure environment variables `PGHOST` was set")
ProcessExit(1)
}
if os.Getenv("PGUSER") == "" {
fmt.Println("Send error: make sure environment variables `PGUSER` was set")
ProcessExit(1)
}
if os.Getenv("PGPASSWORD") == "" {
fmt.Println("Send error: make sure environment variables `PGPASSWORD` was set")
ProcessExit(1)
}
if os.Getenv("PGDATABASE") == "" {
fmt.Println("Send error: make sure environment variables `PGDATABASE` was set")
ProcessExit(1)
}
// if os.Getenv("HOSTNAME") == "" {
// fmt.Println("Send error: make sure environment variables `HOSTNAME` was set")
// ProcessExit(1)
// }
hostname, err := os.Hostname()
if err != nil {
fmt.Println(err)
ProcessExit(1)
}
conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
// fmt.Println(*conn)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
ProcessExit(1)
}
sChain := "auto_blocked"
dChain := "DROP"
// if !IpTablesCreateChain(sChain) {
// log.Println("Iptables chain", sChain, "does not exists")
// ProcessExit(1)
// }
var alarm Alarm
alarm.Hostname = strings.Split(hostname, ".")[0]
alarm.Action_timestamp = time.Unix(time.Now().Unix(), 0)
alarm.Alarm_type = 4
res := PgSelectAlarmRemoteIP(alarm)
var id int32
var ip netip.Prefix
var filter_id int32
for res.Next() {
values, _ := res.Values()
id = values[0].(int32)
ip = values[1].(netip.Prefix)
filter_id = PgSelectFilterIP(ip)
log.Println(filter_id)
if filter_id > 0 {
log.Println("The ip address", ip, "is conained in filter table")
log.Println(PgUpdateAlarm(conn, alarm, id))
// continue
} else {
fmt.Println(id, ip)
if BlockIP(ip, sChain, dChain) {
log.Println("The ip address", ip, "was blocked")
alarm.Alarm_action = true
log.Println(PgUpdateAlarm(conn, alarm, id))
}
}
}
ProcessExit(0)
}