zimbra-alarm/alarmaction-v1.go

321 lines
9.5 KiB
Go
Raw Normal View History

//----------------------------------------------------------------------------
// Получение и обработка данных о пользовательских сессиях (авторизация)
// при соединении с почтовой системой. И блокирование нежелательных адресов.
// ---------------------------------------------------------------------------
// Версия 1 (iptables)
// ---------------------------------------------------------------------------
package main
import (
"log"
"strings"
"net/netip"
"time"
"context"
"fmt"
"os"
"io/ioutil"
"path/filepath"
"github.com/coreos/go-iptables/iptables"
"github.com/jackc/pgx/v5"
// "github.com/jackc/pgtype"
)
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
}
// 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() {
// Создадим и откроем на запись файл для логов
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)
}