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 (
"github.com/luscis/openlan/cmd/api"
"github.com/luscis/openlan/pkg/schema"
"github.com/urfave/cli/v2"
)
@@ -9,30 +10,6 @@ type ACL struct {
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) {
rule := ACLRule{}
app.Command(&cli.Command{
@@ -42,32 +19,7 @@ func (u ACL) Commands(app *api.App) {
&cli.StringFlag{Name: "name", Aliases: []string{"n"}},
},
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(),
{
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
}
func (u ACLRule) Url(prefix, acl, name string) string {
if name == "" {
return prefix + "/api/acl/" + acl
} else {
return prefix + "/api/acl/" + acl + "/" + name
}
func (u ACLRule) Url(prefix, name string) string {
return prefix + "/api/network/" + name + "/acl"
}
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
}
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
}
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 {
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 {
@@ -105,11 +106,11 @@ func (u ACLRule) Commands() *cli.Command {
Name: "add",
Usage: "Add a new acl rule",
Flags: []cli.Flag{
&cli.StringFlag{Name: "src", Aliases: []string{"s"}},
&cli.StringFlag{Name: "dst", Aliases: []string{"d"}},
&cli.StringFlag{Name: "proto", Aliases: []string{"p"}},
&cli.StringFlag{Name: "sport", Aliases: []string{"dp"}},
&cli.StringFlag{Name: "dport", Aliases: []string{"sp"}},
&cli.StringFlag{Name: "source", Aliases: []string{"s"}},
&cli.StringFlag{Name: "destination", Aliases: []string{"d"}},
&cli.StringFlag{Name: "protocol", Aliases: []string{"p"}},
&cli.IntFlag{Name: "sport", Aliases: []string{"dp"}},
&cli.IntFlag{Name: "dport", Aliases: []string{"sp"}},
},
Action: u.Add,
},
@@ -118,11 +119,11 @@ func (u ACLRule) Commands() *cli.Command {
Usage: "remove a new acl rule",
Aliases: []string{"rm"},
Flags: []cli.Flag{
&cli.StringFlag{Name: "src", Aliases: []string{"s"}},
&cli.StringFlag{Name: "dst", Aliases: []string{"d"}},
&cli.StringFlag{Name: "proto", Aliases: []string{"p"}},
&cli.StringFlag{Name: "sport", Aliases: []string{"dp"}},
&cli.StringFlag{Name: "dport", Aliases: []string{"sp"}},
&cli.StringFlag{Name: "source", Aliases: []string{"s"}},
&cli.StringFlag{Name: "destination", Aliases: []string{"d"}},
&cli.StringFlag{Name: "protocol", Aliases: []string{"p"}},
&cli.IntFlag{Name: "sport", Aliases: []string{"dp"}},
&cli.IntFlag{Name: "dport", Aliases: []string{"sp"}},
},
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 {
AddGuest(name, source string) error
DelGuest(name, source string) error
@@ -53,6 +59,7 @@ type Networker interface {
Provider() string
ZTruster() ZTruster
IfAddr() string
ACLer() ACLer
}
var workers = make(map[string]Networker)

View File

@@ -24,4 +24,5 @@ func Add(router *mux.Router, switcher Switcher) {
OpenAPI{}.Router(router)
ZTrust{}.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
table int
br cn.Bridger
acl *ACL
}
func NewWorkerApi(c *co.Network) *WorkerImpl {
@@ -80,6 +81,9 @@ func (w *WorkerImpl) Initialize() {
w.table = w.vrf.Table()
}
w.acl = NewACL(cfg.Name)
w.acl.Initialize()
n := models.Network{
Name: cfg.Name,
IpStart: cfg.Subnet.Start,
@@ -313,12 +317,8 @@ func (w *WorkerImpl) Start(v api.Switcher) {
w.loadVRF()
w.loadRoutes()
if cfg.Acl != "" {
fire.Mangle.Pre.AddRule(cn.IPRule{
Input: cfg.Bridge.Name,
Jump: cfg.Acl,
})
}
w.acl.Start()
w.toACL(cfg.Bridge.Name)
if cfg.Bridge.Mss > 0 {
// forward to remote
@@ -498,6 +498,8 @@ func (w *WorkerImpl) Stop() {
}
w.outputs = nil
w.acl.Stop()
w.setR.Destroy()
w.setV.Destroy()
}
@@ -546,17 +548,15 @@ func (w *WorkerImpl) Subnet() *net.IPNet {
func (w *WorkerImpl) Reload(v api.Switcher) {
}
func (w *WorkerImpl) toACL(acl, input string) {
func (w *WorkerImpl) toACL(input string) {
if input == "" {
return
}
if acl != "" {
w.fire.Mangle.Pre.AddRule(cn.IPRule{
w.fire.Raw.Pre.AddRule(cn.IPRule{
Input: input,
Jump: acl,
Jump: w.acl.Chain(),
})
}
}
func (w *WorkerImpl) openPort(protocol, port, comment string) {
w.out.Info("WorkerImpl.openPort %s %s", protocol, port)
@@ -689,7 +689,7 @@ func (w *WorkerImpl) forwardZone(input string) {
}
func (w *WorkerImpl) forwardVPN() {
cfg, vpn := w.GetCfgs()
_, vpn := w.GetCfgs()
if vpn == nil {
return
}
@@ -706,7 +706,7 @@ func (w *WorkerImpl) forwardVPN() {
// Enable MASQUERADE, and FORWARD it.
w.toRelated(devName, "Accept related")
w.toACL(cfg.Acl, devName)
w.toACL(devName)
for _, rt := range vpn.Routes {
if rt == "0.0.0.0/0" {
@@ -784,3 +784,7 @@ func (w *WorkerImpl) ZTruster() api.ZTruster {
func (w *WorkerImpl) IfAddr() string {
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"
"github.com/luscis/openlan/pkg/libol"
"github.com/luscis/openlan/pkg/network"
cn "github.com/luscis/openlan/pkg/network"
"github.com/luscis/openlan/pkg/schema"
)
@@ -69,7 +68,7 @@ func (g *ZGuest) Chain() string {
}
func (g *ZGuest) Start() {
g.chain = cn.NewFireWallChain(g.Chain(), network.TMangle, "")
g.chain = cn.NewFireWallChain(g.Chain(), cn.TMangle, "")
g.chain.Install()
}
@@ -168,7 +167,7 @@ func (z *ZTrust) Chain() string {
}
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{
Comment: "ZTrust Deny All",
Jump: "DROP",