fea: support add or del acl rule

This commit is contained in:
Daniel Ding
2024-03-27 13:17:41 +08:00
parent 71875619ba
commit 34c452c41d
7 changed files with 322 additions and 84 deletions

View File

@@ -2,6 +2,7 @@ package v5
import ( import (
"github.com/luscis/openlan/cmd/api" "github.com/luscis/openlan/cmd/api"
"github.com/luscis/openlan/pkg/schema"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@@ -9,30 +10,6 @@ type ACL struct {
Cmd Cmd
} }
func (u ACL) Url(prefix, name string) string {
if name == "" {
return prefix + "/api/acl"
} else {
return prefix + "/api/acl/" + name
}
}
func (u ACL) Add(c *cli.Context) error {
return nil
}
func (u ACL) Remove(c *cli.Context) error {
return nil
}
func (u ACL) List(c *cli.Context) error {
return nil
}
func (u ACL) Apply(c *cli.Context) error {
return nil
}
func (u ACL) Commands(app *api.App) { func (u ACL) Commands(app *api.App) {
rule := ACLRule{} rule := ACLRule{}
app.Command(&cli.Command{ app.Command(&cli.Command{
@@ -42,32 +19,7 @@ func (u ACL) Commands(app *api.App) {
&cli.StringFlag{Name: "name", Aliases: []string{"n"}}, &cli.StringFlag{Name: "name", Aliases: []string{"n"}},
}, },
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
{
Name: "add",
Usage: "Add a new acl",
Action: u.Add,
},
{
Name: "remove",
Usage: "Remove an existing acl",
Aliases: []string{"ls"},
Action: u.Remove,
},
{
Name: "list",
Usage: "Display all acl",
Aliases: []string{"ls"},
Action: u.List,
},
rule.Commands(), rule.Commands(),
{
Name: "apply",
Usage: "Apply a new acl",
Flags: []cli.Flag{
&cli.StringFlag{Name: "network", Aliases: []string{"net"}},
},
Action: u.Apply,
},
}, },
}) })
} }
@@ -76,24 +28,73 @@ type ACLRule struct {
Cmd Cmd
} }
func (u ACLRule) Url(prefix, acl, name string) string { func (u ACLRule) Url(prefix, name string) string {
if name == "" { return prefix + "/api/network/" + name + "/acl"
return prefix + "/api/acl/" + acl
} else {
return prefix + "/api/acl/" + acl + "/" + name
}
} }
func (u ACLRule) Add(c *cli.Context) error { func (u ACLRule) Add(c *cli.Context) error {
name := c.String("name")
url := u.Url(c.String("url"), name)
rule := &schema.ACLRule{
Proto: c.String("protocol"),
SrcIp: c.String("source"),
DstIp: c.String("destination"),
SrcPort: c.Int("sport"),
DstPort: c.Int("dport"),
Action: "DROP",
}
clt := u.NewHttp(c.String("token"))
if err := clt.PostJSON(url, rule, nil); err != nil {
return err
}
return nil return nil
} }
func (u ACLRule) Remove(c *cli.Context) error { func (u ACLRule) Remove(c *cli.Context) error {
name := c.String("name")
url := u.Url(c.String("url"), name)
rule := &schema.ACLRule{
Proto: c.String("protocol"),
SrcIp: c.String("source"),
DstIp: c.String("destination"),
SrcPort: c.Int("sport"),
DstPort: c.Int("dport"),
Action: "DROP",
}
clt := u.NewHttp(c.String("token"))
if err := clt.DeleteJSON(url, rule, nil); err != nil {
return err
}
return nil return nil
} }
func (u ACLRule) Tmpl() string {
return `# total {{ len . }}
{{ps -15 "source"}} {{ps -15 "destination"}} {{ps -4 "protocol"}} {{ps -4 "dport"}} {{ps -4 "sport"}}
{{- range . }}
{{ps -15 .SrcIp}} {{ps -15 .DstIp}} {{ps -4 .Proto}} {{pi -4 .DstPort}} {{pi -4 .SrcPort}}
{{- end }}
`
}
func (u ACLRule) List(c *cli.Context) error { func (u ACLRule) List(c *cli.Context) error {
return nil name := c.String("name")
url := u.Url(c.String("url"), name)
clt := u.NewHttp(c.String("token"))
var items []schema.ACLRule
if err := clt.GetJSON(url, &items); err != nil {
return err
}
return u.Out(items, c.String("format"), u.Tmpl())
} }
func (u ACLRule) Commands() *cli.Command { func (u ACLRule) Commands() *cli.Command {
@@ -105,11 +106,11 @@ func (u ACLRule) Commands() *cli.Command {
Name: "add", Name: "add",
Usage: "Add a new acl rule", Usage: "Add a new acl rule",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{Name: "src", Aliases: []string{"s"}}, &cli.StringFlag{Name: "source", Aliases: []string{"s"}},
&cli.StringFlag{Name: "dst", Aliases: []string{"d"}}, &cli.StringFlag{Name: "destination", Aliases: []string{"d"}},
&cli.StringFlag{Name: "proto", Aliases: []string{"p"}}, &cli.StringFlag{Name: "protocol", Aliases: []string{"p"}},
&cli.StringFlag{Name: "sport", Aliases: []string{"dp"}}, &cli.IntFlag{Name: "sport", Aliases: []string{"dp"}},
&cli.StringFlag{Name: "dport", Aliases: []string{"sp"}}, &cli.IntFlag{Name: "dport", Aliases: []string{"sp"}},
}, },
Action: u.Add, Action: u.Add,
}, },
@@ -118,11 +119,11 @@ func (u ACLRule) Commands() *cli.Command {
Usage: "remove a new acl rule", Usage: "remove a new acl rule",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{Name: "src", Aliases: []string{"s"}}, &cli.StringFlag{Name: "source", Aliases: []string{"s"}},
&cli.StringFlag{Name: "dst", Aliases: []string{"d"}}, &cli.StringFlag{Name: "destination", Aliases: []string{"d"}},
&cli.StringFlag{Name: "proto", Aliases: []string{"p"}}, &cli.StringFlag{Name: "protocol", Aliases: []string{"p"}},
&cli.StringFlag{Name: "sport", Aliases: []string{"dp"}}, &cli.IntFlag{Name: "sport", Aliases: []string{"dp"}},
&cli.StringFlag{Name: "dport", Aliases: []string{"sp"}}, &cli.IntFlag{Name: "dport", Aliases: []string{"sp"}},
}, },
Action: u.Remove, Action: u.Remove,
}, },

87
pkg/api/acl.go Executable file
View File

@@ -0,0 +1,87 @@
package api
import (
"net/http"
"github.com/gorilla/mux"
"github.com/luscis/openlan/pkg/schema"
)
type ACL struct {
Switcher Switcher
}
func (h ACL) Router(router *mux.Router) {
router.HandleFunc("/api/network/{id}/acl", h.List).Methods("GET")
router.HandleFunc("/api/network/{id}/acl", h.Add).Methods("POST")
router.HandleFunc("/api/network/{id}/acl", h.Del).Methods("DELETE")
}
func (h ACL) List(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
worker := GetWorker(id)
if worker == nil {
http.Error(w, "Network not found", http.StatusInternalServerError)
return
}
acl := worker.ACLer()
rules := make([]schema.ACLRule, 0, 1024)
acl.ListRules(func(obj schema.ACLRule) {
rules = append(rules, obj)
})
ResponseJson(w, rules)
}
func (h ACL) Add(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
worker := GetWorker(id)
if worker == nil {
http.Error(w, "Network not found", http.StatusInternalServerError)
return
}
acl := worker.ACLer()
rule := &schema.ACLRule{}
if err := GetData(r, rule); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := acl.AddRule(rule); err == nil {
ResponseJson(w, "success")
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (h ACL) Del(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
worker := GetWorker(id)
if worker == nil {
http.Error(w, "Network not found", http.StatusInternalServerError)
return
}
acl := worker.ACLer()
rule := &schema.ACLRule{}
if err := GetData(r, rule); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := acl.DelRule(rule); err == nil {
ResponseJson(w, "success")
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@@ -32,6 +32,12 @@ func NewWorkerSchema(s Switcher) schema.Worker {
} }
} }
type ACLer interface {
AddRule(rule *schema.ACLRule) error
DelRule(rule *schema.ACLRule) error
ListRules(call func(obj schema.ACLRule))
}
type ZTruster interface { type ZTruster interface {
AddGuest(name, source string) error AddGuest(name, source string) error
DelGuest(name, source string) error DelGuest(name, source string) error
@@ -53,6 +59,7 @@ type Networker interface {
Provider() string Provider() string
ZTruster() ZTruster ZTruster() ZTruster
IfAddr() string IfAddr() string
ACLer() ACLer
} }
var workers = make(map[string]Networker) var workers = make(map[string]Networker)

View File

@@ -24,4 +24,5 @@ func Add(router *mux.Router, switcher Switcher) {
OpenAPI{}.Router(router) OpenAPI{}.Router(router)
ZTrust{}.Router(router) ZTrust{}.Router(router)
Output{}.Router(router) Output{}.Router(router)
ACL{}.Router(router)
} }

139
pkg/switch/acl.go Normal file
View File

@@ -0,0 +1,139 @@
package cswitch
import (
"fmt"
"strconv"
"github.com/luscis/openlan/pkg/libol"
cn "github.com/luscis/openlan/pkg/network"
"github.com/luscis/openlan/pkg/schema"
)
type ACLRule struct {
SrcIp string
DstIp string
Proto string // TCP, UDP or ICMP
SrcPort int
DstPort int
Action string // DROP or ACCEPT
rule *cn.IPRule
}
func (r *ACLRule) Id() string {
return fmt.Sprintf("%s:%s:%s:%d:%d", r.SrcIp, r.DstIp, r.Proto, r.DstPort, r.SrcPort)
}
func (r *ACLRule) Rule() cn.IPRule {
if r.rule == nil {
r.rule = &cn.IPRule{
Dest: r.DstIp,
Source: r.SrcIp,
Proto: r.Proto,
Jump: r.Action,
}
if r.DstPort > 0 {
r.rule.DstPort = strconv.Itoa(r.DstPort)
}
if r.SrcPort > 0 {
r.rule.SrcPort = strconv.Itoa(r.SrcPort)
}
}
return *r.rule
}
type ACL struct {
Name string
Rules map[string]*ACLRule
chain *cn.FireWallChain
out *libol.SubLogger
}
func NewACL(name string) *ACL {
return &ACL{
Name: name,
out: libol.NewSubLogger(name),
Rules: make(map[string]*ACLRule, 32),
}
}
func (a *ACL) Chain() string {
return "ATT_" + a.Name
}
func (a *ACL) Initialize() {
a.chain = cn.NewFireWallChain(a.Chain(), cn.TRaw, "")
}
func (a *ACL) Start() {
a.out.Info("ACL.Start")
a.chain.Install()
}
func (a *ACL) Stop() {
a.out.Info("ACL.Stop")
a.chain.Cancel()
}
func (a *ACL) AddRule(rule *schema.ACLRule) error {
ar := &ACLRule{
Proto: rule.Proto,
DstIp: rule.DstIp,
SrcIp: rule.SrcIp,
DstPort: rule.DstPort,
SrcPort: rule.SrcPort,
Action: rule.Action,
}
a.out.Info("ACL.AddRule %s", ar.Id())
if _, ok := a.Rules[ar.Id()]; ok {
return libol.NewErr("AddRule: already existed")
}
if err := a.chain.AddRuleX(ar.Rule()); err == nil {
a.Rules[ar.Id()] = ar
} else {
a.out.Warn("ACL.AddRule %s", err)
}
return nil
}
func (a *ACL) DelRule(rule *schema.ACLRule) error {
ar := &ACLRule{
Proto: rule.Proto,
DstIp: rule.DstIp,
SrcIp: rule.SrcIp,
DstPort: rule.DstPort,
SrcPort: rule.SrcPort,
Action: rule.Action,
}
a.out.Info("ACL.DelRule %s", ar.Id())
if _, ok := a.Rules[ar.Id()]; !ok {
return nil
}
if err := a.chain.DelRuleX(ar.Rule()); err == nil {
delete(a.Rules, ar.Id())
} else {
a.out.Warn("ACL.DelRule %s", err)
}
return nil
}
func (a *ACL) ListRules(call func(obj schema.ACLRule)) {
for _, rule := range a.Rules {
obj := schema.ACLRule{
SrcIp: rule.SrcIp,
DstIp: rule.DstIp,
SrcPort: rule.SrcPort,
DstPort: rule.DstPort,
Proto: rule.Proto,
Action: rule.Action,
}
call(obj)
}
}

View File

@@ -56,6 +56,7 @@ type WorkerImpl struct {
vrf *cn.VRF vrf *cn.VRF
table int table int
br cn.Bridger br cn.Bridger
acl *ACL
} }
func NewWorkerApi(c *co.Network) *WorkerImpl { func NewWorkerApi(c *co.Network) *WorkerImpl {
@@ -80,6 +81,9 @@ func (w *WorkerImpl) Initialize() {
w.table = w.vrf.Table() w.table = w.vrf.Table()
} }
w.acl = NewACL(cfg.Name)
w.acl.Initialize()
n := models.Network{ n := models.Network{
Name: cfg.Name, Name: cfg.Name,
IpStart: cfg.Subnet.Start, IpStart: cfg.Subnet.Start,
@@ -313,12 +317,8 @@ func (w *WorkerImpl) Start(v api.Switcher) {
w.loadVRF() w.loadVRF()
w.loadRoutes() w.loadRoutes()
if cfg.Acl != "" { w.acl.Start()
fire.Mangle.Pre.AddRule(cn.IPRule{ w.toACL(cfg.Bridge.Name)
Input: cfg.Bridge.Name,
Jump: cfg.Acl,
})
}
if cfg.Bridge.Mss > 0 { if cfg.Bridge.Mss > 0 {
// forward to remote // forward to remote
@@ -498,6 +498,8 @@ func (w *WorkerImpl) Stop() {
} }
w.outputs = nil w.outputs = nil
w.acl.Stop()
w.setR.Destroy() w.setR.Destroy()
w.setV.Destroy() w.setV.Destroy()
} }
@@ -546,16 +548,14 @@ func (w *WorkerImpl) Subnet() *net.IPNet {
func (w *WorkerImpl) Reload(v api.Switcher) { func (w *WorkerImpl) Reload(v api.Switcher) {
} }
func (w *WorkerImpl) toACL(acl, input string) { func (w *WorkerImpl) toACL(input string) {
if input == "" { if input == "" {
return return
} }
if acl != "" { w.fire.Raw.Pre.AddRule(cn.IPRule{
w.fire.Mangle.Pre.AddRule(cn.IPRule{
Input: input, Input: input,
Jump: acl, Jump: w.acl.Chain(),
}) })
}
} }
func (w *WorkerImpl) openPort(protocol, port, comment string) { func (w *WorkerImpl) openPort(protocol, port, comment string) {
@@ -689,7 +689,7 @@ func (w *WorkerImpl) forwardZone(input string) {
} }
func (w *WorkerImpl) forwardVPN() { func (w *WorkerImpl) forwardVPN() {
cfg, vpn := w.GetCfgs() _, vpn := w.GetCfgs()
if vpn == nil { if vpn == nil {
return return
} }
@@ -706,7 +706,7 @@ func (w *WorkerImpl) forwardVPN() {
// Enable MASQUERADE, and FORWARD it. // Enable MASQUERADE, and FORWARD it.
w.toRelated(devName, "Accept related") w.toRelated(devName, "Accept related")
w.toACL(cfg.Acl, devName) w.toACL(devName)
for _, rt := range vpn.Routes { for _, rt := range vpn.Routes {
if rt == "0.0.0.0/0" { if rt == "0.0.0.0/0" {
@@ -784,3 +784,7 @@ func (w *WorkerImpl) ZTruster() api.ZTruster {
func (w *WorkerImpl) IfAddr() string { func (w *WorkerImpl) IfAddr() string {
return strings.SplitN(w.cfg.Bridge.Address, "/", 2)[0] return strings.SplitN(w.cfg.Bridge.Address, "/", 2)[0]
} }
func (w *WorkerImpl) ACLer() api.ACLer {
return w.acl
}

View File

@@ -6,7 +6,6 @@ import (
"time" "time"
"github.com/luscis/openlan/pkg/libol" "github.com/luscis/openlan/pkg/libol"
"github.com/luscis/openlan/pkg/network"
cn "github.com/luscis/openlan/pkg/network" cn "github.com/luscis/openlan/pkg/network"
"github.com/luscis/openlan/pkg/schema" "github.com/luscis/openlan/pkg/schema"
) )
@@ -69,7 +68,7 @@ func (g *ZGuest) Chain() string {
} }
func (g *ZGuest) Start() { func (g *ZGuest) Start() {
g.chain = cn.NewFireWallChain(g.Chain(), network.TMangle, "") g.chain = cn.NewFireWallChain(g.Chain(), cn.TMangle, "")
g.chain.Install() g.chain.Install()
} }
@@ -168,7 +167,7 @@ func (z *ZTrust) Chain() string {
} }
func (z *ZTrust) Initialize() { func (z *ZTrust) Initialize() {
z.chain = cn.NewFireWallChain(z.Chain(), network.TMangle, "") z.chain = cn.NewFireWallChain(z.Chain(), cn.TMangle, "")
z.chain.AddRule(cn.IPRule{ z.chain.AddRule(cn.IPRule{
Comment: "ZTrust Deny All", Comment: "ZTrust Deny All",
Jump: "DROP", Jump: "DROP",