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)
|
|||
|
}
|