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