372 lines
11 KiB
Go
372 lines
11 KiB
Go
//----------------------------------------------------------------------------
|
||
// Получение и обработка данных о пользовательских сессиях (авторизация)
|
||
// при соединении с почтовой системой. И блокирование нежелательных адресов.
|
||
// ---------------------------------------------------------------------------
|
||
// Версия 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)
|
||
}
|