mirror of
https://github.com/TeaOSLab/EdgeNode.git
synced 2025-11-06 10:00:25 +08:00
增加IP动作
This commit is contained in:
@@ -20,7 +20,7 @@ func Notify(event string) {
|
|||||||
locker.Lock()
|
locker.Lock()
|
||||||
callbacks, _ := eventsMap[event]
|
callbacks, _ := eventsMap[event]
|
||||||
locker.Unlock()
|
locker.Unlock()
|
||||||
|
|
||||||
for _, callback := range callbacks {
|
for _, callback := range callbacks {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
|
|||||||
25
internal/iplibrary/action_base.go
Normal file
25
internal/iplibrary/action_base.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseAction struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *BaseAction) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *BaseAction) convertParams(params maps.Map, ptr interface{}) error {
|
||||||
|
data, err := json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(data, ptr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
22
internal/iplibrary/action_errors.go
Normal file
22
internal/iplibrary/action_errors.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
// 是否是致命错误
|
||||||
|
type FataError struct {
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FataError) Error() string {
|
||||||
|
return this.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFataError(err string) error {
|
||||||
|
return &FataError{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsFatalError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok := err.(*FataError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
147
internal/iplibrary/action_firewalld.go
Normal file
147
internal/iplibrary/action_firewalld.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Firewalld动作管理
|
||||||
|
// 常用命令:
|
||||||
|
// - 查询列表: firewall-cmd --list-all
|
||||||
|
// - 添加IP:firewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
|
||||||
|
// - 删除IP:firewall-cmd --remove-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
|
||||||
|
type FirewalldAction struct {
|
||||||
|
BaseAction
|
||||||
|
|
||||||
|
config *firewallconfigs.FirewallActionFirewalldConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFirewalldAction() *FirewalldAction {
|
||||||
|
return &FirewalldAction{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FirewalldAction) Init(config *firewallconfigs.FirewallActionConfig) error {
|
||||||
|
this.config = &firewallconfigs.FirewallActionFirewalldConfig{}
|
||||||
|
err := this.convertParams(config.Params, this.config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FirewalldAction) AddItem(listType IPListType, item *pb.IPItem) error {
|
||||||
|
return this.runAction("addItem", listType, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FirewalldAction) DeleteItem(listType IPListType, item *pb.IPItem) error {
|
||||||
|
return this.runAction("deleteItem", listType, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FirewalldAction) runAction(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
|
if item.Type == "all" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(item.IpTo) == 0 {
|
||||||
|
return this.runActionSingleIP(action, listType, item)
|
||||||
|
}
|
||||||
|
cidrList, err := iPv4RangeToCIDRRange(item.IpFrom, item.IpTo)
|
||||||
|
if err != nil {
|
||||||
|
// 不合法的范围不予处理即可
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(cidrList) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, cidr := range cidrList {
|
||||||
|
item.IpFrom = cidr
|
||||||
|
item.IpTo = ""
|
||||||
|
err := this.runActionSingleIP(action, listType, item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *FirewalldAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
|
timestamp := time.Now().Unix()
|
||||||
|
|
||||||
|
if item.ExpiredAt > 0 && timestamp > item.ExpiredAt {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path := this.config.Path
|
||||||
|
var err error
|
||||||
|
if len(path) == 0 {
|
||||||
|
path, err = exec.LookPath("firewall-cmd")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(path) == 0 {
|
||||||
|
return errors.New("can not find 'firewall-cmd'")
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := ""
|
||||||
|
switch action {
|
||||||
|
case "addItem":
|
||||||
|
opt = "--add-rich-rule"
|
||||||
|
case "deleteItem":
|
||||||
|
opt = "--remove-rich-rule"
|
||||||
|
default:
|
||||||
|
return errors.New("invalid action '" + action + "'")
|
||||||
|
}
|
||||||
|
opt += "=rule family='"
|
||||||
|
switch item.Type {
|
||||||
|
case "ipv4":
|
||||||
|
opt += "ipv4"
|
||||||
|
case "ipv6":
|
||||||
|
opt += "ipv6"
|
||||||
|
default:
|
||||||
|
// 我们忽略不能识别的Family
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
opt += "' source address='"
|
||||||
|
if len(item.IpFrom) == 0 {
|
||||||
|
return errors.New("invalid ip from")
|
||||||
|
}
|
||||||
|
opt += item.IpFrom + "' "
|
||||||
|
|
||||||
|
switch listType {
|
||||||
|
case IPListTypeWhite:
|
||||||
|
opt += " accept"
|
||||||
|
case IPListTypeBlack:
|
||||||
|
opt += " reject"
|
||||||
|
default:
|
||||||
|
// 我们忽略不能识别的列表类型
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{opt}
|
||||||
|
if item.ExpiredAt > timestamp {
|
||||||
|
args = append(args, "--timeout="+fmt.Sprintf("%d", item.ExpiredAt-timestamp)+"s")
|
||||||
|
} else {
|
||||||
|
// TODO 思考是否需要permanent,不然--reload之后会丢失
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
// MAC OS直接返回
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cmd := exec.Command(path, args...)
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(err.Error() + ", output: " + string(stderr.Bytes()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
79
internal/iplibrary/action_firewalld_test.go
Normal file
79
internal/iplibrary/action_firewalld_test.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFirewalldAction_AddItem(t *testing.T) {
|
||||||
|
{
|
||||||
|
action := NewFirewalldAction()
|
||||||
|
action.config = &firewallconfigs.FirewallActionFirewalldConfig{
|
||||||
|
Path: "/usr/bin/firewalld",
|
||||||
|
}
|
||||||
|
err := action.AddItem(IPListTypeWhite, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
action := NewFirewalldAction()
|
||||||
|
action.config = &firewallconfigs.FirewallActionFirewalldConfig{
|
||||||
|
Path: "/usr/bin/firewalld",
|
||||||
|
}
|
||||||
|
err := action.AddItem(IPListTypeBlack, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.101",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirewalldAction_DeleteItem(t *testing.T) {
|
||||||
|
action := NewFirewalldAction()
|
||||||
|
action.config = &firewallconfigs.FirewallActionFirewalldConfig{
|
||||||
|
Path: "/usr/bin/firewalld",
|
||||||
|
}
|
||||||
|
err := action.DeleteItem(IPListTypeWhite, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirewalldAction_MultipleItem(t *testing.T) {
|
||||||
|
action := NewFirewalldAction()
|
||||||
|
action.config = &firewallconfigs.FirewallActionFirewalldConfig{
|
||||||
|
Path: "/usr/bin/firewalld",
|
||||||
|
}
|
||||||
|
err := action.AddItem(IPListTypeBlack, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.30",
|
||||||
|
IpTo: "192.168.1.200",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
81
internal/iplibrary/action_http_api.go
Normal file
81
internal/iplibrary/action_http_api.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var httpAPIClient = &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPAPIAction struct {
|
||||||
|
BaseAction
|
||||||
|
|
||||||
|
config *firewallconfigs.FirewallActionHTTPAPIConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPAPIAction() *HTTPAPIAction {
|
||||||
|
return &HTTPAPIAction{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPAPIAction) Init(config *firewallconfigs.FirewallActionConfig) error {
|
||||||
|
this.config = &firewallconfigs.FirewallActionHTTPAPIConfig{}
|
||||||
|
err := this.convertParams(config.Params, this.config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(this.config.URL) == 0 {
|
||||||
|
return NewFataError("'url' should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPAPIAction) AddItem(listType IPListType, item *pb.IPItem) error {
|
||||||
|
return this.runAction("addItem", listType, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPAPIAction) DeleteItem(listType IPListType, item *pb.IPItem) error {
|
||||||
|
return this.runAction("deleteItem", listType, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPAPIAction) runAction(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
|
if item == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 增加节点ID等信息
|
||||||
|
m := maps.Map{
|
||||||
|
"action": action,
|
||||||
|
"listType": listType,
|
||||||
|
"item": maps.Map{
|
||||||
|
"type": item.Type,
|
||||||
|
"ipFrom": item.IpFrom,
|
||||||
|
"ipTo": item.IpTo,
|
||||||
|
"expiredAt": item.ExpiredAt,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mJSON, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPost, this.config.URL, bytes.NewReader(mJSON))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", "GoEdge-Node/"+teaconst.Version)
|
||||||
|
resp, err := httpAPIClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
41
internal/iplibrary/action_http_api_test.go
Normal file
41
internal/iplibrary/action_http_api_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHTTPAPIAction_AddItem(t *testing.T) {
|
||||||
|
action := NewHTTPAPIAction()
|
||||||
|
action.config = &firewallconfigs.FirewallActionHTTPAPIConfig{
|
||||||
|
URL: "http://127.0.0.1:2345/post",
|
||||||
|
TimeoutSeconds: 0,
|
||||||
|
}
|
||||||
|
err := action.AddItem(IPListTypeBlack, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPAPIAction_DeleteItem(t *testing.T) {
|
||||||
|
action := NewHTTPAPIAction()
|
||||||
|
action.config = &firewallconfigs.FirewallActionHTTPAPIConfig{
|
||||||
|
URL: "http://127.0.0.1:2345/post",
|
||||||
|
TimeoutSeconds: 0,
|
||||||
|
}
|
||||||
|
err := action.DeleteItem(IPListTypeBlack, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
20
internal/iplibrary/action_interface.go
Normal file
20
internal/iplibrary/action_interface.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActionInterface interface {
|
||||||
|
// 初始化
|
||||||
|
Init(config *firewallconfigs.FirewallActionConfig) error
|
||||||
|
|
||||||
|
// 添加
|
||||||
|
AddItem(listType IPListType, item *pb.IPItem) error
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
DeleteItem(listType IPListType, item *pb.IPItem) error
|
||||||
|
|
||||||
|
// 关闭
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
276
internal/iplibrary/action_ipset.go
Normal file
276
internal/iplibrary/action_ipset.go
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPSet动作
|
||||||
|
// 相关命令:
|
||||||
|
// - 利用Firewalld管理set:
|
||||||
|
// - 添加:firewall-cmd --permanent --new-ipset=edge_ip_list --type=hash:ip --option="timeout=0"
|
||||||
|
// - 删除:firewall-cmd --permanent --delete-ipset=edge_ip_list
|
||||||
|
// - 重载:firewall-cmd --reload
|
||||||
|
// - firewalld+ipset: firewall-cmd --permanent --add-rich-rule="rule source ipset='edge_ip_list' reject"
|
||||||
|
// - 利用IPTables管理set:
|
||||||
|
// - 添加:iptables -A INPUT -m set --match-set edge_ip_list src -j REJECT
|
||||||
|
// - 添加Item:ipset add edge_ip_list 192.168.2.32 timeout 30
|
||||||
|
// - 删除Item: ipset del edge_ip_list 192.168.2.32
|
||||||
|
// - 创建set:ipset create edge_ip_list hash:ip timeout 0
|
||||||
|
type IPSetAction struct {
|
||||||
|
BaseAction
|
||||||
|
|
||||||
|
config *firewallconfigs.FirewallActionIPSetConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIPSetAction() *IPSetAction {
|
||||||
|
return &IPSetAction{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) error {
|
||||||
|
this.config = &firewallconfigs.FirewallActionIPSetConfig{}
|
||||||
|
err := this.convertParams(config.Params, this.config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(this.config.WhiteName) == 0 {
|
||||||
|
return NewFataError("white list name should not be empty")
|
||||||
|
}
|
||||||
|
if len(this.config.BlackName) == 0 {
|
||||||
|
return NewFataError("black list name should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建ipset
|
||||||
|
{
|
||||||
|
path, err := exec.LookPath("ipset")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cmd := exec.Command(path, "create", this.config.WhiteName, "hash:ip", "timeout", "0")
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
output := stderr.Bytes()
|
||||||
|
if !bytes.Contains(output, []byte("already exists")) {
|
||||||
|
return errors.New("create ipset '" + this.config.WhiteName + "': " + err.Error() + ", output: " + string(output))
|
||||||
|
} else {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cmd := exec.Command(path, "create", this.config.BlackName, "hash:ip", "timeout", "0")
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
output := stderr.Bytes()
|
||||||
|
if !bytes.Contains(output, []byte("already exists")) {
|
||||||
|
return errors.New("create ipset '" + this.config.BlackName + "': " + err.Error() + ", output: " + string(output))
|
||||||
|
} else {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// firewalld
|
||||||
|
if this.config.AutoAddToFirewalld {
|
||||||
|
path, err := exec.LookPath("firewall-cmd")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd := exec.Command(path, "--permanent", "--new-ipset="+this.config.WhiteName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000")
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
output := stderr.Bytes()
|
||||||
|
if bytes.Contains(output, []byte("NAME_CONFLICT")) {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
return errors.New("firewall-cmd add ipset '" + this.config.WhiteName + "': " + err.Error() + ", output: " + string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd := exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+this.config.WhiteName+"' accept")
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
output := stderr.Bytes()
|
||||||
|
return errors.New("firewall-cmd add rich rule '" + this.config.WhiteName + "': " + err.Error() + ", output: " + string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd := exec.Command(path, "--permanent", "--new-ipset="+this.config.BlackName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000")
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
output := stderr.Bytes()
|
||||||
|
if bytes.Contains(output, []byte("NAME_CONFLICT")) {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
return errors.New("firewall-cmd add ipset '" + this.config.BlackName + "': " + err.Error() + ", output: " + string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd := exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+this.config.BlackName+"' reject")
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
output := stderr.Bytes()
|
||||||
|
return errors.New("firewall-cmd add rich rule '" + this.config.WhiteName + "': " + err.Error() + ", output: " + string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reload
|
||||||
|
{
|
||||||
|
cmd := exec.Command(path, "--reload")
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
output := stderr.Bytes()
|
||||||
|
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// iptables
|
||||||
|
if this.config.AutoAddToIPTables {
|
||||||
|
path, err := exec.LookPath("iptables")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd := exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", this.config.WhiteName, "src", "-j", "ACCEPT")
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
output := stderr.Bytes()
|
||||||
|
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd := exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", this.config.BlackName, "src", "-j", "REJECT")
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
output := stderr.Bytes()
|
||||||
|
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IPSetAction) AddItem(listType IPListType, item *pb.IPItem) error {
|
||||||
|
return this.runAction("addItem", listType, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IPSetAction) DeleteItem(listType IPListType, item *pb.IPItem) error {
|
||||||
|
return this.runAction("deleteItem", listType, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IPSetAction) runAction(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
|
if item.Type == "all" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(item.IpTo) == 0 {
|
||||||
|
return this.runActionSingleIP(action, listType, item)
|
||||||
|
}
|
||||||
|
cidrList, err := iPv4RangeToCIDRRange(item.IpFrom, item.IpTo)
|
||||||
|
if err != nil {
|
||||||
|
// 不合法的范围不予处理即可
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(cidrList) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, cidr := range cidrList {
|
||||||
|
item.IpFrom = cidr
|
||||||
|
item.IpTo = ""
|
||||||
|
err := this.runActionSingleIP(action, listType, item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
|
if item.Type == "all" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
listName := ""
|
||||||
|
switch listType {
|
||||||
|
case IPListTypeWhite:
|
||||||
|
listName = this.config.WhiteName
|
||||||
|
case IPListTypeBlack:
|
||||||
|
listName = this.config.BlackName
|
||||||
|
default:
|
||||||
|
// 不支持的类型
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(listName) == 0 {
|
||||||
|
return errors.New("empty list name for '" + listType + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
path := this.config.Path
|
||||||
|
var err error
|
||||||
|
if len(path) == 0 {
|
||||||
|
path, err = exec.LookPath("ipset")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipset add edge_ip_list 192.168.2.32 timeout 30
|
||||||
|
args := []string{}
|
||||||
|
switch action {
|
||||||
|
case "addItem":
|
||||||
|
args = append(args, "add")
|
||||||
|
case "deleteItem":
|
||||||
|
args = append(args, "del")
|
||||||
|
}
|
||||||
|
args = append(args, listName, item.IpFrom)
|
||||||
|
timestamp := time.Now().Unix()
|
||||||
|
if item.ExpiredAt > timestamp {
|
||||||
|
args = append(args, "timeout", strconv.FormatInt(item.ExpiredAt-timestamp, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
//logs.Println(args)
|
||||||
|
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
// MAC OS直接返回
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(path, args...)
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
81
internal/iplibrary/action_ipset_test.go
Normal file
81
internal/iplibrary/action_ipset_test.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIPSetAction_Init(t *testing.T) {
|
||||||
|
action := NewIPSetAction()
|
||||||
|
err := action.Init(&firewallconfigs.FirewallActionConfig{
|
||||||
|
Params: maps.Map{
|
||||||
|
"path": "/usr/bin/iptables",
|
||||||
|
"whiteName": "white-list",
|
||||||
|
"blackName": "black-list",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPSetAction_AddItem(t *testing.T) {
|
||||||
|
action := NewIPSetAction()
|
||||||
|
action.config = &firewallconfigs.FirewallActionIPSetConfig{
|
||||||
|
Path: "/usr/bin/iptables",
|
||||||
|
WhiteName: "white-list",
|
||||||
|
BlackName: "black-list",
|
||||||
|
}
|
||||||
|
{
|
||||||
|
err := action.AddItem(IPListTypeWhite, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
err := action.AddItem(IPListTypeBlack, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPSetAction_DeleteItem(t *testing.T) {
|
||||||
|
action := NewIPSetAction()
|
||||||
|
err := action.Init(&firewallconfigs.FirewallActionConfig{
|
||||||
|
Params: maps.Map{
|
||||||
|
"path": "/usr/bin/firewalld",
|
||||||
|
"whiteName": "white-list",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = action.DeleteItem(IPListTypeWhite, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
119
internal/iplibrary/action_iptables.go
Normal file
119
internal/iplibrary/action_iptables.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPTables动作
|
||||||
|
// 相关命令:
|
||||||
|
// iptables -A INPUT -s "192.168.2.32" -j ACCEPT
|
||||||
|
// iptables -A INPUT -s "192.168.2.32" -j REJECT
|
||||||
|
// iptables -D ...
|
||||||
|
type IPTablesAction struct {
|
||||||
|
BaseAction
|
||||||
|
|
||||||
|
config *firewallconfigs.FirewallActionIPTablesConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIPTablesAction() *IPTablesAction {
|
||||||
|
return &IPTablesAction{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IPTablesAction) Init(config *firewallconfigs.FirewallActionConfig) error {
|
||||||
|
this.config = &firewallconfigs.FirewallActionIPTablesConfig{}
|
||||||
|
err := this.convertParams(config.Params, this.config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IPTablesAction) AddItem(listType IPListType, item *pb.IPItem) error {
|
||||||
|
return this.runAction("addItem", listType, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IPTablesAction) DeleteItem(listType IPListType, item *pb.IPItem) error {
|
||||||
|
return this.runAction("deleteItem", listType, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IPTablesAction) runAction(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
|
if item.Type == "all" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(item.IpTo) == 0 {
|
||||||
|
return this.runActionSingleIP(action, listType, item)
|
||||||
|
}
|
||||||
|
cidrList, err := iPv4RangeToCIDRRange(item.IpFrom, item.IpTo)
|
||||||
|
if err != nil {
|
||||||
|
// 不合法的范围不予处理即可
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(cidrList) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, cidr := range cidrList {
|
||||||
|
item.IpFrom = cidr
|
||||||
|
item.IpTo = ""
|
||||||
|
err := this.runActionSingleIP(action, listType, item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
|
if item.Type == "all" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
path := this.config.Path
|
||||||
|
var err error
|
||||||
|
if len(path) == 0 {
|
||||||
|
path, err = exec.LookPath("iptables")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iptablesAction := ""
|
||||||
|
switch action {
|
||||||
|
case "addItem":
|
||||||
|
iptablesAction = "-A"
|
||||||
|
case "deleteItem":
|
||||||
|
iptablesAction = "-D"
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
args := []string{iptablesAction, "INPUT", "-s", item.IpFrom, "-j"}
|
||||||
|
switch listType {
|
||||||
|
case IPListTypeWhite:
|
||||||
|
args = append(args, "ACCEPT")
|
||||||
|
case IPListTypeBlack:
|
||||||
|
args = append(args, "REJECT")
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
// MAC OS直接返回
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(path, args...)
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
output := stderr.Bytes()
|
||||||
|
if bytes.Contains(output, []byte("No chain/target/match")) {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
return errors.New(err.Error() + ", output: " + string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
57
internal/iplibrary/action_iptables_test.go
Normal file
57
internal/iplibrary/action_iptables_test.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIPTablesAction_AddItem(t *testing.T) {
|
||||||
|
action := NewIPTablesAction()
|
||||||
|
action.config = &firewallconfigs.FirewallActionIPTablesConfig{
|
||||||
|
Path: "/usr/bin/iptables",
|
||||||
|
}
|
||||||
|
{
|
||||||
|
err := action.AddItem(IPListTypeWhite, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
err := action.AddItem(IPListTypeBlack, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPTablesAction_DeleteItem(t *testing.T) {
|
||||||
|
action := NewIPTablesAction()
|
||||||
|
action.config = &firewallconfigs.FirewallActionIPTablesConfig{
|
||||||
|
Path: "/usr/bin/firewalld",
|
||||||
|
}
|
||||||
|
err := action.DeleteItem(IPListTypeWhite, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
ExpiredAt: time.Now().Unix() + 30,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
164
internal/iplibrary/action_manager.go
Normal file
164
internal/iplibrary/action_manager.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var SharedActionManager = NewActionManager()
|
||||||
|
|
||||||
|
// 动作管理器定义
|
||||||
|
type ActionManager struct {
|
||||||
|
locker sync.Mutex
|
||||||
|
|
||||||
|
eventMap map[string][]ActionInterface // eventLevel => []instance
|
||||||
|
configMap map[int64]*firewallconfigs.FirewallActionConfig // id => config
|
||||||
|
instanceMap map[int64]ActionInterface // id => instance
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取动作管理对象
|
||||||
|
func NewActionManager() *ActionManager {
|
||||||
|
return &ActionManager{
|
||||||
|
configMap: map[int64]*firewallconfigs.FirewallActionConfig{},
|
||||||
|
instanceMap: map[int64]ActionInterface{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新配置
|
||||||
|
func (this *ActionManager) UpdateActions(actions []*firewallconfigs.FirewallActionConfig) {
|
||||||
|
this.locker.Lock()
|
||||||
|
defer this.locker.Unlock()
|
||||||
|
|
||||||
|
// 关闭不存在的
|
||||||
|
newActionsMap := map[int64]*firewallconfigs.FirewallActionConfig{}
|
||||||
|
for _, action := range actions {
|
||||||
|
newActionsMap[action.Id] = action
|
||||||
|
}
|
||||||
|
for _, oldAction := range this.configMap {
|
||||||
|
_, ok := newActionsMap[oldAction.Id]
|
||||||
|
if !ok {
|
||||||
|
instance, ok := this.instanceMap[oldAction.Id]
|
||||||
|
if ok {
|
||||||
|
_ = instance.Close()
|
||||||
|
delete(this.instanceMap, oldAction.Id)
|
||||||
|
remotelogs.Println("IPLIBRARY/ACTION_MANAGER", "close action "+strconv.FormatInt(oldAction.Id, 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新的或者更新老的
|
||||||
|
for _, newAction := range newActionsMap {
|
||||||
|
oldInstance, ok := this.instanceMap[newAction.Id]
|
||||||
|
if ok {
|
||||||
|
// 检查配置是否一致
|
||||||
|
oldConfigJSON, err := json.Marshal(this.configMap[newAction.Id])
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "action "+strconv.FormatInt(newAction.Id, 10) + ", type:" + newAction.Type+": "+err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newConfigJSON, err := json.Marshal(newAction)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "action "+strconv.FormatInt(newAction.Id, 10) + ", type:" + newAction.Type+": "+err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if bytes.Compare(newConfigJSON, oldConfigJSON) != 0 {
|
||||||
|
_ = oldInstance.Close()
|
||||||
|
|
||||||
|
// 重新创建
|
||||||
|
// 之所以要重新创建,是因为前后的动作类型可能有变化,完全重建可以避免不必要的麻烦
|
||||||
|
newInstance, err := this.createInstance(newAction)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "reload action "+strconv.FormatInt(newAction.Id, 10) + ", type:" + newAction.Type+": "+err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
remotelogs.Println("IPLIBRARY/ACTION_MANAGER", "reloaded "+strconv.FormatInt(newAction.Id, 10)+", type:"+newAction.Type)
|
||||||
|
this.instanceMap[newAction.Id] = newInstance
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 创建
|
||||||
|
instance, err := this.createInstance(newAction)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "load new action "+strconv.FormatInt(newAction.Id, 10) + ", type:" + newAction.Type+": "+err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
remotelogs.Println("IPLIBRARY/ACTION_MANAGER", "loaded action "+strconv.FormatInt(newAction.Id, 10)+", type:"+newAction.Type)
|
||||||
|
this.instanceMap[newAction.Id] = instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新配置
|
||||||
|
this.configMap = newActionsMap
|
||||||
|
this.eventMap = map[string][]ActionInterface{}
|
||||||
|
for _, action := range this.configMap {
|
||||||
|
instance, ok := this.instanceMap[action.Id]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
instances, _ := this.eventMap[action.EventLevel]
|
||||||
|
instances = append(instances, instance)
|
||||||
|
this.eventMap[action.EventLevel] = instances
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行添加IP动作
|
||||||
|
func (this *ActionManager) AddItem(listType IPListType, item *pb.IPItem) {
|
||||||
|
instances, ok := this.eventMap[item.EventLevel]
|
||||||
|
if ok {
|
||||||
|
for _, instance := range instances {
|
||||||
|
err := instance.AddItem(listType, item)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "add item '"+fmt.Sprintf("%d", item.Id)+"': "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行删除IP动作
|
||||||
|
func (this *ActionManager) DeleteItem(listType IPListType, item *pb.IPItem) {
|
||||||
|
instances, ok := this.eventMap[item.EventLevel]
|
||||||
|
if ok {
|
||||||
|
for _, instance := range instances {
|
||||||
|
err := instance.DeleteItem(listType, item)
|
||||||
|
if err != nil {
|
||||||
|
remotelogs.Error("IPLIBRARY/ACTION_MANAGER", "delete item '"+fmt.Sprintf("%d", item.Id)+"': "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ActionManager) createInstance(config *firewallconfigs.FirewallActionConfig) (ActionInterface, error) {
|
||||||
|
var instance ActionInterface
|
||||||
|
switch config.Type {
|
||||||
|
case firewallconfigs.FirewallActionTypeIPSet:
|
||||||
|
instance = NewIPSetAction()
|
||||||
|
case firewallconfigs.FirewallActionTypeFirewalld:
|
||||||
|
instance = NewFirewalldAction()
|
||||||
|
case firewallconfigs.FirewallActionTypeIPTables:
|
||||||
|
instance = NewIPTablesAction()
|
||||||
|
case firewallconfigs.FirewallActionTypeScript:
|
||||||
|
instance = NewScriptAction()
|
||||||
|
case firewallconfigs.FirewallActionTypeHTTPAPI:
|
||||||
|
instance = NewHTTPAPIAction()
|
||||||
|
}
|
||||||
|
if instance == nil {
|
||||||
|
return nil, errors.New("can not create instance for type '" + config.Type + "'")
|
||||||
|
}
|
||||||
|
err := instance.Init(config)
|
||||||
|
if err != nil {
|
||||||
|
// 如果是警告错误,我们只是提示
|
||||||
|
if !IsFatalError(err) {
|
||||||
|
remotelogs.Error("IPLIBRARY/ACTION_MANAGER/CREATE_INSTANCE", "init '"+config.Type+"' failed: "+err.Error())
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance, nil
|
||||||
|
}
|
||||||
56
internal/iplibrary/action_manager_test.go
Normal file
56
internal/iplibrary/action_manager_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestActionManager_UpdateActions(t *testing.T) {
|
||||||
|
manager := NewActionManager()
|
||||||
|
manager.UpdateActions([]*firewallconfigs.FirewallActionConfig{
|
||||||
|
{
|
||||||
|
Id: 1,
|
||||||
|
Type: "ipset",
|
||||||
|
Params: maps.Map{
|
||||||
|
"whiteName": "edge-white-list",
|
||||||
|
"blackName": "edge-black-list",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
t.Log("===config===")
|
||||||
|
for _, c := range manager.configMap {
|
||||||
|
t.Log(c.Id, c.Type)
|
||||||
|
}
|
||||||
|
t.Log("===instance===")
|
||||||
|
for id, c := range manager.instanceMap {
|
||||||
|
t.Log(id, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.UpdateActions([]*firewallconfigs.FirewallActionConfig{
|
||||||
|
{
|
||||||
|
Id: 1,
|
||||||
|
Type: "ipset",
|
||||||
|
Params: maps.Map{
|
||||||
|
"whiteName": "edge-white-list",
|
||||||
|
"blackName": "edge-black-list",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: 2,
|
||||||
|
Type: "iptables",
|
||||||
|
Params: maps.Map{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Log("===config===")
|
||||||
|
for _, c := range manager.configMap {
|
||||||
|
t.Log(c.Id, c.Type)
|
||||||
|
}
|
||||||
|
t.Log("===instance===")
|
||||||
|
for id, c := range manager.instanceMap {
|
||||||
|
t.Logf("%d: %#v", id, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
69
internal/iplibrary/action_script.go
Normal file
69
internal/iplibrary/action_script.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 脚本命令动作
|
||||||
|
type ScriptAction struct {
|
||||||
|
BaseAction
|
||||||
|
|
||||||
|
config *firewallconfigs.FirewallActionScriptConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScriptAction() *ScriptAction {
|
||||||
|
return &ScriptAction{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ScriptAction) Init(config *firewallconfigs.FirewallActionConfig) error {
|
||||||
|
this.config = &firewallconfigs.FirewallActionScriptConfig{}
|
||||||
|
err := this.convertParams(config.Params, this.config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(this.config.Path) == 0 {
|
||||||
|
return NewFataError("'path' should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ScriptAction) AddItem(listType IPListType, item *pb.IPItem) error {
|
||||||
|
return this.runAction("addItem", listType, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ScriptAction) DeleteItem(listType IPListType, item *pb.IPItem) error {
|
||||||
|
return this.runAction("deleteItem", listType, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ScriptAction) runAction(action string, listType IPListType, item *pb.IPItem) error {
|
||||||
|
// TODO 智能支持 .sh 脚本文件
|
||||||
|
cmd := exec.Command(this.config.Path)
|
||||||
|
cmd.Env = []string{
|
||||||
|
"ACTION=" + action,
|
||||||
|
"TYPE=" + item.Type,
|
||||||
|
"IP_FROM=" + item.IpFrom,
|
||||||
|
"IP_TO=" + item.IpTo,
|
||||||
|
"EXPIRED_AT=" + fmt.Sprintf("%d", item.ExpiredAt),
|
||||||
|
"LIST_TYPE=" + listType,
|
||||||
|
}
|
||||||
|
if len(this.config.Cwd) > 0 {
|
||||||
|
cmd.Dir = this.config.Cwd
|
||||||
|
} else {
|
||||||
|
cmd.Dir = filepath.Dir(this.config.Path)
|
||||||
|
}
|
||||||
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(err.Error() + ", output: " + string(stderr.Bytes()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
46
internal/iplibrary/action_script_test.go
Normal file
46
internal/iplibrary/action_script_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestScriptAction_AddItem(t *testing.T) {
|
||||||
|
action := NewScriptAction()
|
||||||
|
action.config = &firewallconfigs.FirewallActionScriptConfig{
|
||||||
|
Path: "/tmp/ip-item.sh",
|
||||||
|
Cwd: "",
|
||||||
|
Args: nil,
|
||||||
|
}
|
||||||
|
err := action.AddItem(IPListTypeBlack, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
ExpiredAt: time.Now().Unix(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScriptAction_DeleteItem(t *testing.T) {
|
||||||
|
action := NewScriptAction()
|
||||||
|
action.config = &firewallconfigs.FirewallActionScriptConfig{
|
||||||
|
Path: "/tmp/ip-item.sh",
|
||||||
|
Cwd: "",
|
||||||
|
Args: nil,
|
||||||
|
}
|
||||||
|
err := action.DeleteItem(IPListTypeBlack, &pb.IPItem{
|
||||||
|
Type: "ipv4",
|
||||||
|
Id: 1,
|
||||||
|
IpFrom: "192.168.1.100",
|
||||||
|
ExpiredAt: time.Now().Unix(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("ok")
|
||||||
|
}
|
||||||
85
internal/iplibrary/action_utils.go
Normal file
85
internal/iplibrary/action_utils.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Convert IPv4 range into CIDR
|
||||||
|
// 来自:https://gist.github.com/P-A-R-U-S/a090dd90c5104ce85a29c32669dac107
|
||||||
|
func iPv4RangeToCIDRRange(ipStart string, ipEnd string) (cidrs []string, err error) {
|
||||||
|
|
||||||
|
cidr2mask := []uint32{
|
||||||
|
0x00000000, 0x80000000, 0xC0000000,
|
||||||
|
0xE0000000, 0xF0000000, 0xF8000000,
|
||||||
|
0xFC000000, 0xFE000000, 0xFF000000,
|
||||||
|
0xFF800000, 0xFFC00000, 0xFFE00000,
|
||||||
|
0xFFF00000, 0xFFF80000, 0xFFFC0000,
|
||||||
|
0xFFFE0000, 0xFFFF0000, 0xFFFF8000,
|
||||||
|
0xFFFFC000, 0xFFFFE000, 0xFFFFF000,
|
||||||
|
0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00,
|
||||||
|
0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0,
|
||||||
|
0xFFFFFFE0, 0xFFFFFFF0, 0xFFFFFFF8,
|
||||||
|
0xFFFFFFFC, 0xFFFFFFFE, 0xFFFFFFFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
ipStartUint32 := iPv4ToUint32(ipStart)
|
||||||
|
ipEndUint32 := iPv4ToUint32(ipEnd)
|
||||||
|
|
||||||
|
if ipStartUint32 > ipEndUint32 {
|
||||||
|
log.Fatalf("start IP:%s must be less than end IP:%s", ipStart, ipEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
for ipEndUint32 >= ipStartUint32 {
|
||||||
|
maxSize := 32
|
||||||
|
for maxSize > 0 {
|
||||||
|
|
||||||
|
maskedBase := ipStartUint32 & cidr2mask[maxSize-1]
|
||||||
|
|
||||||
|
if maskedBase != ipStartUint32 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
maxSize--
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
x := math.Log(float64(ipEndUint32-ipStartUint32+1)) / math.Log(2)
|
||||||
|
maxDiff := 32 - int(math.Floor(x))
|
||||||
|
if maxSize < maxDiff {
|
||||||
|
maxSize = maxDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
cidrs = append(cidrs, uInt32ToIPv4(ipStartUint32)+"/"+strconv.Itoa(maxSize))
|
||||||
|
|
||||||
|
ipStartUint32 += uint32(math.Exp2(float64(32 - maxSize)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cidrs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert IPv4 to uint32
|
||||||
|
func iPv4ToUint32(iPv4 string) uint32 {
|
||||||
|
|
||||||
|
ipOctets := [4]uint64{}
|
||||||
|
|
||||||
|
for i, v := range strings.SplitN(iPv4, ".", 4) {
|
||||||
|
ipOctets[i], _ = strconv.ParseUint(v, 10, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := (ipOctets[0] << 24) | (ipOctets[1] << 16) | (ipOctets[2] << 8) | ipOctets[3]
|
||||||
|
|
||||||
|
return uint32(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert uint32 to IP
|
||||||
|
func uInt32ToIPv4(iPuInt32 uint32) (iP string) {
|
||||||
|
iP = fmt.Sprintf("%d.%d.%d.%d",
|
||||||
|
iPuInt32>>24,
|
||||||
|
(iPuInt32&0x00FFFFFF)>>16,
|
||||||
|
(iPuInt32&0x0000FFFF)>>8,
|
||||||
|
iPuInt32&0x000000FF)
|
||||||
|
return iP
|
||||||
|
}
|
||||||
7
internal/iplibrary/action_utils_test.go
Normal file
7
internal/iplibrary/action_utils_test.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIPv4RangeToCIDRRange(t *testing.T) {
|
||||||
|
t.Log(iPv4RangeToCIDRRange("192.168.0.0", "192.168.255.255"))
|
||||||
|
}
|
||||||
@@ -12,11 +12,12 @@ const (
|
|||||||
|
|
||||||
// IP条目
|
// IP条目
|
||||||
type IPItem struct {
|
type IPItem struct {
|
||||||
Type string
|
Type string `json:"type"`
|
||||||
Id int64
|
Id int64 `json:"id"`
|
||||||
IPFrom uint64
|
IPFrom uint64 `json:"ipFrom"`
|
||||||
IPTo uint64
|
IPTo uint64 `json:"ipTo"`
|
||||||
ExpiredAt int64
|
ExpiredAt int64 `json:"expiredAt"`
|
||||||
|
EventLevel string `json:"eventLevel"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否包含某个IP
|
// 检查是否包含某个IP
|
||||||
|
|||||||
8
internal/iplibrary/list_type.go
Normal file
8
internal/iplibrary/list_type.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package iplibrary
|
||||||
|
|
||||||
|
type IPListType = string
|
||||||
|
|
||||||
|
const (
|
||||||
|
IPListTypeWhite IPListType = "white"
|
||||||
|
IPListTypeBlack IPListType = "black"
|
||||||
|
)
|
||||||
@@ -120,15 +120,25 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
|
|||||||
}
|
}
|
||||||
if item.IsDeleted {
|
if item.IsDeleted {
|
||||||
list.Delete(item.Id)
|
list.Delete(item.Id)
|
||||||
|
|
||||||
|
// 操作事件
|
||||||
|
SharedActionManager.DeleteItem(item.ListType, item)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Add(&IPItem{
|
list.Add(&IPItem{
|
||||||
Id: item.Id,
|
Id: item.Id,
|
||||||
Type: item.Type,
|
Type: item.Type,
|
||||||
IPFrom: utils.IP2Long(item.IpFrom),
|
IPFrom: utils.IP2Long(item.IpFrom),
|
||||||
IPTo: utils.IP2Long(item.IpTo),
|
IPTo: utils.IP2Long(item.IpTo),
|
||||||
ExpiredAt: item.ExpiredAt,
|
ExpiredAt: item.ExpiredAt,
|
||||||
|
EventLevel: item.EventLevel,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 事件操作
|
||||||
|
SharedActionManager.DeleteItem(item.ListType, item)
|
||||||
|
SharedActionManager.AddItem(item.ListType, item)
|
||||||
}
|
}
|
||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
this.version = items[len(items)-1].Version
|
this.version = items[len(items)-1].Version
|
||||||
|
|||||||
@@ -369,6 +369,7 @@ func (this *Node) syncConfig() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sharedWAFManager.UpdatePolicies(nodeConfig.FindAllFirewallPolicies())
|
sharedWAFManager.UpdatePolicies(nodeConfig.FindAllFirewallPolicies())
|
||||||
|
iplibrary.SharedActionManager.UpdateActions(nodeConfig.FirewallActions)
|
||||||
sharedNodeConfig = nodeConfig
|
sharedNodeConfig = nodeConfig
|
||||||
|
|
||||||
// 发送事件
|
// 发送事件
|
||||||
|
|||||||
Reference in New Issue
Block a user