diff --git a/README.cn.md b/README.cn.md index fc1b4ff..fc177b7 100755 --- a/README.cn.md +++ b/README.cn.md @@ -127,3 +127,4 @@ OpenLAN提供一种局域网数据报文在广域网的传输实现,并能够 - [多区域互联](docs/multiarea.md) - [全互连网络](docs/fabric.md) - [IPSec网络](docs/ipsec.md) +- [零信任网络](docs/ztrust.md) diff --git a/README.en.md b/README.en.md index 32c9adf..039a678 100755 --- a/README.en.md +++ b/README.en.md @@ -124,3 +124,4 @@ If you have more flexible VPN business needs and need to use VPN to access the e - [Multi-region Interconnection](docs/multiarea.md) - [Fullly Interconnected Network](docs/fabric.md) - [IPSec Network](docs/ipsec.md) +- [Zero Trust Network](docs/ztrust.md) diff --git a/cmd/api/utils.go b/cmd/api/utils.go index bd8994e..4da9867 100755 --- a/cmd/api/utils.go +++ b/cmd/api/utils.go @@ -13,6 +13,14 @@ func GetEnv(key, value string) string { return val } +func GetUser(name string) string { + values := strings.SplitN(name, ":", 2) + if strings.Contains(values[0], "@") { + return values[0] + } + return "" +} + func SplitName(name string) (string, string) { values := strings.SplitN(name, "@", 2) if len(values) == 2 { diff --git a/cmd/api/v5/client.go b/cmd/api/v5/client.go index 9fd58fb..cf82dd8 100755 --- a/cmd/api/v5/client.go +++ b/cmd/api/v5/client.go @@ -3,10 +3,12 @@ package v5 import ( "bytes" "encoding/json" - "github.com/luscis/openlan/cmd/api" - "github.com/luscis/openlan/pkg/libol" "io/ioutil" "net/http" + "strings" + + "github.com/luscis/openlan/cmd/api" + "github.com/luscis/openlan/pkg/libol" ) type Client struct { @@ -104,9 +106,16 @@ type Cmd struct { } func (c Cmd) NewHttp(token string) Client { + values := strings.SplitN(token, ":", 2) + username := values[0] + password := values[0] + if len(values) == 2 { + password = values[1] + } client := Client{ Auth: libol.Auth{ - Username: token, + Username: username, + Password: password, }, } return client diff --git a/cmd/api/v5/guest.go b/cmd/api/v5/guest.go index f4bc47f..2371773 100755 --- a/cmd/api/v5/guest.go +++ b/cmd/api/v5/guest.go @@ -15,8 +15,8 @@ type Guest struct { func (u Guest) Url(prefix, name string) string { name, network := api.SplitName(name) - if name == "" { - return prefix + "/api/network/" + network + "/guest" + if network == "" { + return prefix + "/api/network/" + name + "/guest" } return prefix + "/api/network/" + network + "/guest/" + name } @@ -69,7 +69,7 @@ func (u Guest) Tmpl() string { func (u Guest) List(c *cli.Context) error { network := c.String("network") - url := u.Url(c.String("url"), "@"+network) + url := u.Url(c.String("url"), network) clt := u.NewHttp(c.String("token")) var items []schema.ZGuest @@ -81,6 +81,7 @@ func (u Guest) List(c *cli.Context) error { } func (u Guest) Commands(app *api.App) { + name := api.GetUser(api.Token) app.Command(&cli.Command{ Name: "guest", Aliases: []string{"gu"}, @@ -90,7 +91,7 @@ func (u Guest) Commands(app *api.App) { Name: "add", Usage: "Add a zGuest", Flags: []cli.Flag{ - &cli.StringFlag{Name: "name"}, + &cli.StringFlag{Name: "name", Value: name}, &cli.StringFlag{Name: "address"}, }, Action: u.Add, @@ -100,7 +101,7 @@ func (u Guest) Commands(app *api.App) { Usage: "Remove an existing zGuest", Aliases: []string{"rm"}, Flags: []cli.Flag{ - &cli.StringFlag{Name: "name"}, + &cli.StringFlag{Name: "name", Value: name}, &cli.StringFlag{Name: "address"}, }, Action: u.Remove, @@ -110,7 +111,7 @@ func (u Guest) Commands(app *api.App) { Usage: "Display all zGuests", Aliases: []string{"ls"}, Flags: []cli.Flag{ - &cli.StringFlag{Name: "network"}, + &cli.StringFlag{Name: "network", Value: name}, }, Action: u.List, }, diff --git a/cmd/api/v5/knock.go b/cmd/api/v5/knock.go index e5ffef7..0eda46f 100755 --- a/cmd/api/v5/knock.go +++ b/cmd/api/v5/knock.go @@ -83,6 +83,7 @@ func (u Knock) List(c *cli.Context) error { } func (u Knock) Commands(app *api.App) { + name := api.GetUser(api.Token) app.Command(&cli.Command{ Name: "knock", Aliases: []string{"kn"}, @@ -92,7 +93,7 @@ func (u Knock) Commands(app *api.App) { Name: "add", Usage: "Add a knock", Flags: []cli.Flag{ - &cli.StringFlag{Name: "name"}, + &cli.StringFlag{Name: "name", Value: name}, &cli.StringFlag{Name: "protocol"}, &cli.StringFlag{Name: "socket"}, &cli.IntFlag{Name: "age", Value: 60}, @@ -104,7 +105,7 @@ func (u Knock) Commands(app *api.App) { Usage: "Remove an existing knock", Aliases: []string{"rm"}, Flags: []cli.Flag{ - &cli.StringFlag{Name: "name"}, + &cli.StringFlag{Name: "name", Value: name}, &cli.StringFlag{Name: "protocol"}, &cli.StringFlag{Name: "socket"}, }, @@ -115,7 +116,7 @@ func (u Knock) Commands(app *api.App) { Usage: "Display all knock", Aliases: []string{"ls"}, Flags: []cli.Flag{ - &cli.StringFlag{Name: "name"}, + &cli.StringFlag{Name: "name", Value: name}, }, Action: u.List, }, diff --git a/docs/ztrust.md b/docs/ztrust.md new file mode 100644 index 0000000..9e0c10a --- /dev/null +++ b/docs/ztrust.md @@ -0,0 +1,42 @@ +# Zero Trust + +## Enable ztrust on a network +``` +$ cat /etc/openlan/switch/network/example.json +{ + ... + "ztrust": "enable" +} +$ +``` + +## Add yourself to ztrust +``` +$ export TOKEN="daniel@example:g4nlzmk5nxek1hbcqsbr" +$ export URL="https://your-central-switch-address:10000" +$ openlan guest add +$ openlan guest ls +# total 1 +username address +daniel@internal 169.254.15.6 +$ +``` + +## Knock a host service +``` +$ openlan knock add --protocol icmp --socket 192.168.20.10 +$ openlan knock add --protocol tcp --socket 192.168.20.10:22 +$ openlan knock ls +# total 2 +username protocol socket age createAt +daniel@internal tcp 192.168.20.10:22 57 2024-01-02 12:42:06 +0000 UTC +daniel@internal icmp 192.168.20.10: 46 2024-01-02 12:41:55 +0000 UTC +$ +``` + +## Connect to a host service +``` +$ ssh root@192.168.20.10 who +root pts/0 2024-01-02 06:49 (100.66.88.1) +$ +``` \ No newline at end of file diff --git a/pkg/api/ztrust.go b/pkg/api/ztrust.go index e4ceab4..e8f54bb 100755 --- a/pkg/api/ztrust.go +++ b/pkg/api/ztrust.go @@ -2,6 +2,7 @@ package api import ( "net/http" + "strings" "github.com/gorilla/mux" "github.com/luscis/openlan/pkg/cache" @@ -16,12 +17,21 @@ type ZTrust struct { func (h ZTrust) Router(router *mux.Router) { router.HandleFunc("/api/network/{id}/ztrust", h.List).Methods("GET") router.HandleFunc("/api/network/{id}/guest", h.ListGuest).Methods("GET") + router.HandleFunc("/api/network/{id}/guest/{user}", h.ListGuest).Methods("GET") router.HandleFunc("/api/network/{id}/guest/{user}", h.AddGuest).Methods("POST") router.HandleFunc("/api/network/{id}/guest/{user}", h.DelGuest).Methods("DELETE") router.HandleFunc("/api/network/{id}/guest/{user}/knock", h.ListKnock).Methods("GET") router.HandleFunc("/api/network/{id}/guest/{user}/knock", h.AddKnock).Methods("POST") } +func CheckUser(r *http.Request) (bool, string) { + user, _, _ := r.BasicAuth() + if strings.Contains(user, "@") { + return false, strings.SplitN(user, "@", 2)[0] + } + return true, "" +} + func (h ZTrust) List(w http.ResponseWriter, r *http.Request) { ResponseJson(w, "TODO") } @@ -47,9 +57,17 @@ func (h ZTrust) ListGuest(w http.ResponseWriter, r *http.Request) { return } + admin, name := CheckUser(r) guests := make([]schema.ZGuest, 0, 1024) ztrust.ListGuest(func(obj schema.ZGuest) { - guests = append(guests, obj) + if !admin { + if obj.Name == name { + guests = append(guests, obj) + } + } else { + guests = append(guests, obj) + } + }) ResponseJson(w, guests) @@ -77,7 +95,10 @@ func (h ZTrust) AddGuest(w http.ResponseWriter, r *http.Request) { } guest.Name = vars["user"] - libol.Info("ZTrust.AddGuest %s@%s", guest.Name, id) + admin, name := CheckUser(r) + if !admin { + guest.Name = name + } if guest.Address == "" { client := cache.VPNClient.Get(id, guest.Name) if client != nil { @@ -85,6 +106,9 @@ func (h ZTrust) AddGuest(w http.ResponseWriter, r *http.Request) { guest.Device = client.Device } } + + libol.Info("ZTrust.AddGuest %s@%s", guest.Name, id) + if guest.Address == "" { http.Error(w, "invalid address", http.StatusBadRequest) return @@ -120,6 +144,11 @@ func (h ZTrust) DelGuest(w http.ResponseWriter, r *http.Request) { } guest.Name = vars["user"] + admin, name := CheckUser(r) + if !admin { + guest.Name = name + } + libol.Info("ZTrust.DelGuest %s@%s", guest.Name, id) if err := ztrust.DelGuest(guest.Name, guest.Address); err == nil { ResponseJson(w, "success") @@ -144,9 +173,14 @@ func (h ZTrust) ListKnock(w http.ResponseWriter, r *http.Request) { return } - name := vars["user"] + user := vars["user"] + admin, name := CheckUser(r) + if !admin { + user = name + } + rules := make([]schema.KnockRule, 0, 1024) - ztrust.ListKnock(name, func(obj schema.KnockRule) { + ztrust.ListKnock(user, func(obj schema.KnockRule) { rules = append(rules, obj) }) @@ -173,10 +207,15 @@ func (h ZTrust) AddKnock(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - name := vars["user"] - libol.Info("ZTrust.AddKnock %s@%s", rule.Name, id) - if err := ztrust.Knock(name, rule.Protocol, rule.Dest, rule.Port, rule.Age); err == nil { + user := vars["user"] + admin, name := CheckUser(r) + if !admin { + user = name + } + libol.Info("ZTrust.AddKnock %s@%s", user, id) + + if err := ztrust.Knock(user, rule.Protocol, rule.Dest, rule.Port, rule.Age); err == nil { ResponseJson(w, "success") } else { http.Error(w, err.Error(), http.StatusInternalServerError)