Feat rego default policy (#700)

Converts rules to Rego policies and allow users to write raw policies to set up connectivity and firewall on the clients.
This commit is contained in:
Givi Khojanashvili 2023-03-13 15:14:18 +01:00 committed by GitHub
parent 221934447e
commit 3bfa26b13b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1740 additions and 890 deletions

27
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/onsi/gomega v1.18.1
github.com/pion/ice/v2 v2.3.1
github.com/rs/cors v1.8.0
github.com/sirupsen/logrus v1.8.1
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
github.com/vishvananda/netlink v1.1.0
@ -22,7 +22,7 @@ require (
golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
golang.zx2c4.com/wireguard/windows v0.5.1
google.golang.org/grpc v1.43.0
google.golang.org/grpc v1.52.3
google.golang.org/protobuf v1.28.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)
@ -41,10 +41,11 @@ require (
github.com/libp2p/go-netroute v0.2.0
github.com/magiconair/properties v1.8.5
github.com/mattn/go-sqlite3 v1.14.16
github.com/miekg/dns v1.1.41
github.com/miekg/dns v1.1.43
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/open-policy-agent/opa v0.49.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/prometheus/client_golang v1.13.0
github.com/prometheus/client_golang v1.14.0
github.com/rs/xid v1.3.0
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/stretchr/testify v1.8.1
@ -58,7 +59,9 @@ require (
require (
github.com/BurntSushi/toml v0.4.1 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
@ -66,25 +69,27 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mdlayher/genetlink v1.1.0 // indirect
github.com/mdlayher/netlink v1.4.2 // indirect
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect
@ -100,14 +105,19 @@ require (
github.com/pion/turn/v2 v2.1.0 // indirect
github.com/pion/udp/v2 v2.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.1.0 // indirect
github.com/yuin/goldmark v1.4.13 // indirect
go.opentelemetry.io/otel v1.11.1 // indirect
go.opentelemetry.io/otel/sdk v1.11.1 // indirect
@ -120,8 +130,7 @@ require (
golang.org/x/tools v0.6.0 // indirect
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

89
go.sum
View File

@ -39,13 +39,16 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10=
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -55,7 +58,8 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -63,13 +67,14 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA=
github.com/c-robinson/iplib v1.0.3 h1:NG0UF0GoEsrC1/vyfX1Lx2Ss7CySWl3KqqXh3q4DdPU=
github.com/c-robinson/iplib v1.0.3/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -80,12 +85,6 @@ github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U=
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
@ -98,9 +97,12 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
@ -112,22 +114,20 @@ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897 h1:E52jfcE64UG42SwLmrW0QByONfGynWuzBvm86BoB9z8=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
@ -142,6 +142,7 @@ github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
@ -186,11 +187,14 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
@ -202,6 +206,7 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -228,8 +233,10 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -274,7 +281,6 @@ github.com/gopherjs/gopherjs v0.0.0-20220410123724-9e86199038b0 h1:fWY+zXdWhvWnd
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -315,6 +321,7 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@ -338,8 +345,9 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60 h1:tHdB+hQRHU10CfcK0furo6rSNgZ38JT8uPh70c/pFD8=
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60/go.mod h1:aYbhishWc4Ai3I2U4Gaa2n3kHWSwzme6EsG/46HRQbE=
@ -362,8 +370,8 @@ github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/
github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb h1:2dC7L10LmTqlyMVzFJ00qM25lqESg9Z4u3GuEXN5iHY=
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
@ -406,6 +414,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/open-policy-agent/opa v0.49.0 h1:TIlpCT1B5FSm8Dqo/a4t23gKmHkQysC3+7W77F99P4k=
github.com/open-policy-agent/opa v0.49.0/go.mod h1:WTLWtu498/QNTDkiHx76Xj7jaJUPvLJAPtdMkCcst0w=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
@ -444,13 +454,14 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
@ -464,7 +475,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
@ -478,13 +490,12 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smartystreets/assertions v1.13.0 h1:Dx1kYM01xsSqKPno3aqLnrwac2LetPvN23diwyr69Qs=
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
@ -514,6 +525,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
@ -521,6 +534,12 @@ github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJ
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg=
github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -536,6 +555,7 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
go.opentelemetry.io/otel/exporters/prometheus v0.33.0 h1:xXhPj7SLKWU5/Zd4Hxmd+X1C4jdmvc0Xy+kvjFx2z60=
@ -548,7 +568,6 @@ go.opentelemetry.io/otel/sdk/metric v0.33.0 h1:oTqyWfksgKoJmbrs2q7O7ahkJzt+Ipeki
go.opentelemetry.io/otel/sdk/metric v0.33.0/go.mod h1:xdypMeA21JBOvjjzDUtD0kzIcHO/SPez+a8HOzJPGp0=
go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -709,7 +728,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -767,7 +785,9 @@ golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -911,7 +931,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@ -919,8 +938,8 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 h1:a2S6M0+660BgMNl++4JPlcAO/CjkqYItDEZwkoDQK7c=
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -933,11 +952,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ=
google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -959,7 +975,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=

View File

@ -71,11 +71,10 @@ type AccountManager interface {
GroupAddPeer(accountId, groupID, peerID string) error
GroupDeletePeer(accountId, groupID, peerKey string) error
GroupListPeers(accountId, groupID string) ([]*Peer, error)
GetRule(accountID, ruleID, userID string) (*Rule, error)
SaveRule(accountID, userID string, rule *Rule) error
UpdateRule(accountID string, ruleID string, operations []RuleUpdateOperation) (*Rule, error)
DeleteRule(accountID, ruleID, userID string) error
ListRules(accountID, userID string) ([]*Rule, error)
GetPolicy(accountID, policyID, userID string) (*Policy, error)
SavePolicy(accountID, userID string, policy *Policy) error
DeletePolicy(accountID, policyID, userID string) error
ListPolicies(accountID, userID string) ([]*Policy, error)
GetRoute(accountID, routeID, userID string) (*route.Route, error)
CreateRoute(accountID string, prefix, peerID, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error)
SaveRoute(accountID, userID string, route *route.Route) error
@ -94,8 +93,8 @@ type AccountManager interface {
SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error
GetPeer(accountID, peerID, userID string) (*Peer, error)
UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error)
LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) //used by peer gRPC API
SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) //used by peer gRPC API
LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) // used by peer gRPC API
SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) // used by peer gRPC API
}
type DefaultAccountManager struct {
@ -152,6 +151,7 @@ type Account struct {
Users map[string]*User
Groups map[string]*Group
Rules map[string]*Rule
Policies []*Policy
Routes map[string]*route.Route
NameServerGroups map[string]*nbdns.NameServerGroup
DNSSettings *DNSSettings
@ -265,42 +265,6 @@ func (a *Account) GetPeerByIP(peerIP string) *Peer {
return nil
}
// GetPeerRules returns a list of source or destination rules of a given peer.
func (a *Account) GetPeerRules(peerID string) (srcRules []*Rule, dstRules []*Rule) {
// Rules are group based so there is no direct access to peers.
// First, find all groups that the given peer belongs to
peerGroups := make(map[string]struct{})
for s, group := range a.Groups {
for _, peer := range group.Peers {
if peerID == peer {
peerGroups[s] = struct{}{}
break
}
}
}
// Second, find all rules that have discovered source and destination groups
srcRulesMap := make(map[string]*Rule)
dstRulesMap := make(map[string]*Rule)
for _, rule := range a.Rules {
for _, g := range rule.Source {
if _, ok := peerGroups[g]; ok && srcRulesMap[rule.ID] == nil {
srcRules = append(srcRules, rule)
srcRulesMap[rule.ID] = rule
}
}
for _, g := range rule.Destination {
if _, ok := peerGroups[g]; ok && dstRulesMap[rule.ID] == nil {
dstRules = append(dstRules, rule)
dstRulesMap[rule.ID] = rule
}
}
}
return srcRules, dstRules
}
// GetGroup returns a group by ID if exists, nil otherwise
func (a *Account) GetGroup(groupID string) *Group {
return a.Groups[groupID]
@ -308,7 +272,7 @@ func (a *Account) GetGroup(groupID string) *Group {
// GetPeerNetworkMap returns a group by ID if exists, nil otherwise
func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap {
aclPeers := a.getPeersByACL(peerID)
aclPeers, _ := a.getPeersByPolicy(peerID)
// exclude expired peers
var peersToConnect []*Peer
var expiredPeers []*Peer
@ -364,7 +328,6 @@ func (a *Account) GetExpiredPeers() []*Peer {
// If there is no peer that expires this function returns false and a duration of 0.
// This function only considers peers that haven't been expired yet and that are connected.
func (a *Account) GetNextPeerExpiration() (time.Duration, bool) {
peersWithExpiry := a.GetPeersWithExpiration()
if len(peersWithExpiry) == 0 {
return 0, false
@ -456,7 +419,6 @@ func (a *Account) FindPeerByPubKey(peerPubKey string) (*Peer, error) {
// FindUserPeers returns a list of peers that user owns (created)
func (a *Account) FindUserPeers(userID string) ([]*Peer, error) {
peers := make([]*Peer, 0)
for _, peer := range a.Peers {
if peer.UserID == userID {
@ -576,6 +538,11 @@ func (a *Account) Copy() *Account {
rules[id] = rule.Copy()
}
policies := []*Policy{}
for _, policy := range a.Policies {
policies = append(policies, policy.Copy())
}
routes := map[string]*route.Route{}
for id, route := range a.Routes {
routes[id] = route.Copy()
@ -608,6 +575,7 @@ func (a *Account) Copy() *Account {
Users: users,
Groups: groups,
Rules: rules,
Policies: policies,
Routes: routes,
NameServerGroups: nsGroups,
DNSSettings: dnsSettings,
@ -665,7 +633,9 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage
_, err := account.GetGroupAll()
if err != nil {
addAllGroup(account)
if err := addAllGroup(account); err != nil {
return nil, err
}
shouldSave = true
}
@ -701,7 +671,6 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage
// User that performs the update has to belong to the account.
// Returns an updated Account
func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error) {
halfYearLimit := 180 * 24 * time.Hour
if newSettings.PeerLoginExpiration > halfYearLimit {
return nil, status.Errorf(status.InvalidArgument, "peer login expiration can't be larger than 180 days")
@ -884,10 +853,6 @@ func (am *DefaultAccountManager) addAccountIDToIDPAppMeta(userID string, account
}
err = am.idpManager.UpdateUserAppMetadata(userID, idp.AppMetadata{WTAccountID: account.Id})
if err != nil {
return err
}
if err != nil {
return status.Errorf(status.Internal, "updating user's app metadata failed with: %v", err)
}
@ -1276,7 +1241,7 @@ func (am *DefaultAccountManager) GetDNSDomain() string {
}
// addAllGroup to account object if it doesn't exists
func addAllGroup(account *Account) {
func addAllGroup(account *Account) error {
if len(account.Groups) == 0 {
allGroup := &Group{
ID: xid.New().String(),
@ -1296,7 +1261,15 @@ func addAllGroup(account *Account) {
Destination: []string{allGroup.ID},
}
account.Rules = map[string]*Rule{defaultRule.ID: defaultRule}
// TODO: after migration we need to drop rule and create policy directly
defaultPolicy, err := RuleToPolicy(defaultRule)
if err != nil {
return fmt.Errorf("convert rule to policy: %w", err)
}
account.Policies = []*Policy{defaultPolicy}
}
return nil
}
// newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id
@ -1337,7 +1310,9 @@ func newAccountWithId(accountId, userId, domain string) *Account {
},
}
addAllGroup(acc)
if err := addAllGroup(acc); err != nil {
log.Errorf("error adding all group to account %s: %v", acc.Id, err)
}
return acc
}

View File

@ -2,15 +2,16 @@ package server
import (
"fmt"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/route"
"net"
"reflect"
"sync"
"testing"
"time"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/route"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -215,7 +216,6 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
}
func TestNewAccount(t *testing.T) {
domain := "netbird.io"
userId := "account_creator"
accountID := "account_id"
@ -839,10 +839,20 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
}
rule := Rule{
Source: []string{"group-id"},
Destination: []string{"group-id"},
Flow: TrafficFlowBidirect,
policy := Policy{
Enabled: true,
Rules: []*PolicyRule{
{
Enabled: true,
Sources: []string{"group-id"},
Destinations: []string{"group-id"},
Action: PolicyTrafficActionAccept,
},
},
}
if err := policy.UpdateQueryFromRules(); err != nil {
t.Errorf("update policy query from rules: %v", err)
return
}
wg := sync.WaitGroup{}
@ -866,7 +876,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
wg.Wait()
})
t.Run("delete rule update", func(t *testing.T) {
t.Run("delete policy update", func(t *testing.T) {
wg.Add(1)
go func() {
defer wg.Done()
@ -878,12 +888,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
}
}()
var defaultRule *Rule
for _, r := range account.Rules {
defaultRule = r
}
if err := manager.DeleteRule(account.Id, defaultRule.ID, userID); err != nil {
if err := manager.DeletePolicy(account.Id, account.Policies[0].ID, userID); err != nil {
t.Errorf("delete default rule: %v", err)
return
}
@ -891,7 +896,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
wg.Wait()
})
t.Run("save rule update", func(t *testing.T) {
t.Run("save policy update", func(t *testing.T) {
wg.Add(1)
go func() {
defer wg.Done()
@ -903,7 +908,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
}
}()
if err := manager.SaveRule(account.Id, userID, &rule); err != nil {
if err := manager.SavePolicy(account.Id, userID, &policy); err != nil {
t.Errorf("delete default rule: %v", err)
return
}
@ -944,7 +949,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
}()
if err := manager.DeleteGroup(account.Id, group.ID); err != nil {
t.Errorf("delete group rule: %v", err)
t.Errorf("delete group: %v", err)
return
}
@ -1011,6 +1016,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
assert.Equal(t, peer.IP.String(), ev.TargetID)
assert.Equal(t, peer.IP.String(), fmt.Sprint(ev.Meta["ip"]))
}
func getEvent(t *testing.T, accountID string, manager AccountManager, eventType activity.Activity) *activity.Event {
for {
select {
@ -1063,77 +1069,6 @@ func TestGetUsersFromAccount(t *testing.T) {
}
}
func TestAccount_GetPeerRules(t *testing.T) {
groups := map[string]*Group{
"group_1": {
ID: "group_1",
Name: "group_1",
Peers: []string{"peer-1", "peer-2"},
},
"group_2": {
ID: "group_2",
Name: "group_2",
Peers: []string{"peer-2", "peer-3"},
},
"group_3": {
ID: "group_3",
Name: "group_3",
Peers: []string{"peer-4"},
},
"group_4": {
ID: "group_4",
Name: "group_4",
Peers: []string{"peer-1"},
},
"group_5": {
ID: "group_5",
Name: "group_5",
Peers: []string{"peer-1"},
},
}
rules := map[string]*Rule{
"rule-1": {
ID: "rule-1",
Name: "rule-1",
Description: "rule-1",
Disabled: false,
Source: []string{"group_1", "group_5"},
Destination: []string{"group_2"},
Flow: 0,
},
"rule-2": {
ID: "rule-2",
Name: "rule-2",
Description: "rule-2",
Disabled: false,
Source: []string{"group_1"},
Destination: []string{"group_1"},
Flow: 0,
},
"rule-3": {
ID: "rule-3",
Name: "rule-3",
Description: "rule-3",
Disabled: false,
Source: []string{"group_3"},
Destination: []string{"group_3"},
Flow: 0,
},
}
account := &Account{
Groups: groups,
Rules: rules,
}
srcRules, dstRules := account.GetPeerRules("peer-1")
assert.Equal(t, 2, len(srcRules))
assert.Equal(t, 1, len(dstRules))
}
func TestFileStore_GetRoutesByPrefix(t *testing.T) {
_, prefix, err := route.ParseNetwork("192.168.64.0/24")
if err != nil {
@ -1284,6 +1219,12 @@ func TestAccount_Copy(t *testing.T) {
ID: "rule1",
},
},
Policies: []*Policy{
{
ID: "policy1",
Enabled: true,
},
},
Routes: map[string]*route.Route{
"route1": {
ID: "route1",
@ -1326,6 +1267,7 @@ func hasNilField(x interface{}) error {
}
return nil
}
func TestDefaultAccountManager_DefaultAccountSettings(t *testing.T) {
manager, err := createManager(t)
require.NoError(t, err, "unable to create account manager")
@ -1337,6 +1279,7 @@ func TestDefaultAccountManager_DefaultAccountSettings(t *testing.T) {
assert.Equal(t, account.Settings.PeerLoginExpirationEnabled, true)
assert.Equal(t, account.Settings.PeerLoginExpiration, 24*time.Hour)
}
func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) {
manager, err := createManager(t)
require.NoError(t, err, "unable to create account manager")
@ -1355,7 +1298,8 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) {
require.NoError(t, err, "unable to mark peer connected")
account, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
PeerLoginExpiration: time.Hour,
PeerLoginExpirationEnabled: true})
PeerLoginExpirationEnabled: true,
})
require.NoError(t, err, "expecting to update account settings successfully but got error")
wg := &sync.WaitGroup{}
@ -1401,7 +1345,8 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing.
require.NoError(t, err, "unable to add peer")
_, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
PeerLoginExpiration: time.Hour,
PeerLoginExpirationEnabled: true})
PeerLoginExpirationEnabled: true,
})
require.NoError(t, err, "expecting to update account settings successfully but got error")
wg := &sync.WaitGroup{}
@ -1423,7 +1368,6 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing.
if failed {
t.Fatal("timeout while waiting for test to finish")
}
}
func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *testing.T) {
@ -1456,7 +1400,8 @@ func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *test
// enabling PeerLoginExpirationEnabled should trigger the expiration job
account, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
PeerLoginExpiration: time.Hour,
PeerLoginExpirationEnabled: true})
PeerLoginExpirationEnabled: true,
})
require.NoError(t, err, "expecting to update account settings successfully but got error")
failed := waitTimeout(wg, time.Second)
@ -1468,7 +1413,8 @@ func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *test
// disabling PeerLoginExpirationEnabled should trigger cancel
_, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
PeerLoginExpiration: time.Hour,
PeerLoginExpirationEnabled: false})
PeerLoginExpirationEnabled: false,
})
require.NoError(t, err, "expecting to update account settings successfully but got error")
failed = waitTimeout(wg, time.Second)
if failed {
@ -1485,7 +1431,8 @@ func TestDefaultAccountManager_UpdateAccountSettings(t *testing.T) {
updated, err := manager.UpdateAccountSettings(account.Id, userID, &Settings{
PeerLoginExpiration: time.Hour,
PeerLoginExpirationEnabled: false})
PeerLoginExpirationEnabled: false,
})
require.NoError(t, err, "expecting to update account settings successfully but got error")
assert.False(t, updated.Settings.PeerLoginExpirationEnabled)
assert.Equal(t, updated.Settings.PeerLoginExpiration, time.Hour)
@ -1498,12 +1445,14 @@ func TestDefaultAccountManager_UpdateAccountSettings(t *testing.T) {
_, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
PeerLoginExpiration: time.Second,
PeerLoginExpirationEnabled: false})
PeerLoginExpirationEnabled: false,
})
require.Error(t, err, "expecting to fail when providing PeerLoginExpiration less than one hour")
_, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
PeerLoginExpiration: time.Hour * 24 * 181,
PeerLoginExpirationEnabled: false})
PeerLoginExpirationEnabled: false,
})
require.Error(t, err, "expecting to fail when providing PeerLoginExpiration more than 180 days")
}
@ -1590,7 +1539,6 @@ func TestAccount_GetExpiredPeers(t *testing.T) {
}
})
}
}
func TestAccount_GetPeersWithExpiration(t *testing.T) {
@ -1656,11 +1604,9 @@ func TestAccount_GetPeersWithExpiration(t *testing.T) {
}
})
}
}
func TestAccount_GetNextPeerExpiration(t *testing.T) {
type test struct {
name string
peers map[string]*Peer
@ -1784,10 +1730,8 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
} else {
assert.Equal(t, expiration, testCase.expectedNextExpiration)
}
})
}
}
func createManager(t *testing.T) (*DefaultAccountManager, error) {

View File

@ -19,6 +19,12 @@ const (
RuleUpdated
// RuleRemoved indicates that a user removed a rule
RuleRemoved
// PolicyAdded indicates that a user added a new policy
PolicyAdded
// PolicyUpdated indicates that a user updated a policy
PolicyUpdated
// PolicyRemoved indicates that a user removed a policy
PolicyRemoved
// SetupKeyCreated indicates that a user created a new setup key
SetupKeyCreated
// SetupKeyUpdated indicates that a user updated a setup key
@ -84,11 +90,11 @@ const (
PeerAddedByUserMessage string = "Peer added"
// PeerAddedWithSetupKeyMessage is a human-readable text message of the PeerAddedWithSetupKey activity
PeerAddedWithSetupKeyMessage = PeerAddedByUserMessage
//UserJoinedMessage is a human-readable text message of the UserJoined activity
// UserJoinedMessage is a human-readable text message of the UserJoined activity
UserJoinedMessage string = "User joined"
//UserInvitedMessage is a human-readable text message of the UserInvited activity
// UserInvitedMessage is a human-readable text message of the UserInvited activity
UserInvitedMessage string = "User invited"
//AccountCreatedMessage is a human-readable text message of the AccountCreated activity
// AccountCreatedMessage is a human-readable text message of the AccountCreated activity
AccountCreatedMessage string = "Account created"
// PeerRemovedByUserMessage is a human-readable text message of the PeerRemovedByUser activity
PeerRemovedByUserMessage string = "Peer deleted"
@ -98,6 +104,12 @@ const (
RuleRemovedMessage string = "Rule deleted"
// RuleUpdatedMessage is a human-readable text message of the RuleRemoved activity
RuleUpdatedMessage string = "Rule updated"
// PolicyAddedMessage is a human-readable text message of the PolicyAdded activity
PolicyAddedMessage string = "Policy added"
// PolicyRemovedMessage is a human-readable text message of the PolicyRemoved activity
PolicyRemovedMessage string = "Policy deleted"
// PolicyUpdatedMessage is a human-readable text message of the PolicyRemoved activity
PolicyUpdatedMessage string = "Policy updated"
// SetupKeyCreatedMessage is a human-readable text message of the SetupKeyCreated activity
SetupKeyCreatedMessage string = "Setup key created"
// SetupKeyUpdatedMessage is a human-readable text message of the SetupKeyUpdated activity
@ -182,6 +194,12 @@ func (a Activity) Message() string {
return RuleRemovedMessage
case RuleUpdated:
return RuleUpdatedMessage
case PolicyAdded:
return PolicyAddedMessage
case PolicyRemoved:
return PolicyRemovedMessage
case PolicyUpdated:
return PolicyUpdatedMessage
case SetupKeyCreated:
return SetupKeyCreatedMessage
case SetupKeyUpdated:
@ -266,6 +284,12 @@ func (a Activity) StringCode() string {
return "rule.delete"
case RuleUpdated:
return "rule.update"
case PolicyAdded:
return "policy.add"
case PolicyRemoved:
return "policy.delete"
case PolicyUpdated:
return "policy.update"
case SetupKeyCreated:
return "setupkey.add"
case SetupKeyRevoked:

View File

@ -1,15 +1,16 @@
package server
import (
"github.com/netbirdio/netbird/management/server/status"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/netbirdio/netbird/management/server/status"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/util"
)
@ -81,7 +82,6 @@ func restore(file string) (*FileStore, error) {
store.PeerID2AccountID = make(map[string]string)
for accountID, account := range store.Accounts {
if account.Settings == nil {
account.Settings = &Settings{
PeerLoginExpirationEnabled: false,
@ -113,6 +113,20 @@ func restore(file string) (*FileStore, error) {
store.PrivateDomain2AccountID[account.Domain] = accountID
}
// if no policies are defined, that means we need to migrate Rules to policies
if len(account.Policies) == 0 {
account.Policies = make([]*Policy, 0)
for _, rule := range account.Rules {
policy, err := RuleToPolicy(rule)
if err != nil {
log.Errorf("unable to migrate rule to policy: %v", err)
continue
}
account.Policies = append(account.Policies, policy)
}
account.Rules = nil
}
// for data migration. Can be removed once most base will be with labels
existingLabels := account.getPeerDNSLabels()
if len(existingLabels) != len(account.Peers) {
@ -153,7 +167,6 @@ func restore(file string) (*FileStore, error) {
}
if len(migrationPeers) > 0 {
// swap Peer.Key with Peer.ID in the Account.Peers map.
for key, peer := range migrationPeers {
delete(account.Peers, key)
@ -169,6 +182,7 @@ func restore(file string) (*FileStore, error) {
}
}
}
// detect routes that have Peer.Key as a reference and replace it with ID.
for _, route := range account.Routes {
if peer, ok := migrationPeers[route.Peer]; ok {

View File

@ -1,13 +1,14 @@
package server
import (
"github.com/netbirdio/netbird/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net"
"path/filepath"
"testing"
"time"
"github.com/netbirdio/netbird/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type accounts struct {
@ -70,7 +71,6 @@ func TestNewStore(t *testing.T) {
if store.UserID2AccountID == nil || len(store.UserID2AccountID) != 0 {
t.Errorf("expected to create a new empty UserID2AccountID map when creating a new FileStore")
}
}
func TestSaveAccount(t *testing.T) {
@ -109,7 +109,6 @@ func TestSaveAccount(t *testing.T) {
if store.SetupKeyID2AccountID[setupKey.Key] == "" {
t.Errorf("expecting SetupKeyID2AccountID index updated after SaveAccount()")
}
}
func TestStore(t *testing.T) {
@ -156,7 +155,6 @@ func TestStore(t *testing.T) {
if restoredAccount != nil && restoredAccount.Network == nil {
t.Errorf("failed to restore a FileStore file - missing Network")
}
}
func TestRestore(t *testing.T) {
@ -191,6 +189,43 @@ func TestRestore(t *testing.T) {
require.Len(t, store.PrivateDomain2AccountID, 1, "failed to restore a FileStore wrong PrivateDomain2AccountID mapping length")
}
func TestRestorePolicies_Migration(t *testing.T) {
storeDir := t.TempDir()
err := util.CopyFileContents("testdata/store_policy_migrate.json", filepath.Join(storeDir, "store.json"))
if err != nil {
t.Fatal(err)
}
store, err := NewFileStore(storeDir)
if err != nil {
return
}
account := store.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
require.Len(t, account.Groups, 1, "failed to restore a FileStore file - missing Account Groups")
require.Len(t, account.Rules, 0, "failed to restore a FileStore file - Account Rules should be removed")
require.Len(t, account.Policies, 1, "failed to restore a FileStore file - missing Account Policies")
policy := account.Policies[0]
require.Equal(t, policy.Name, "Default", "failed to restore a FileStore file - missing Account Policies Name")
require.Equal(t, policy.Description,
"This is a default rule that allows connections between all the resources",
"failed to restore a FileStore file - missing Account Policies Description")
expectedPolicy := policy.Copy()
err = expectedPolicy.UpdateQueryFromRules()
require.NoError(t, err, "failed to upldate query")
require.Equal(t, policy.Query, expectedPolicy.Query, "failed to restore a FileStore file - missing Account Policies Query")
require.Len(t, policy.Rules, 1, "failed to restore a FileStore file - missing Account Policy Rules")
require.Equal(t, policy.Rules[0].Action, PolicyTrafficActionAccept, "failed to restore a FileStore file - missing Account Policies Action")
require.Equal(t, policy.Rules[0].Destinations,
[]string{"cfefqs706sqkneg59g3g"},
"failed to restore a FileStore file - missing Account Policies Destinations")
require.Equal(t, policy.Rules[0].Sources,
[]string{"cfefqs706sqkneg59g3g"},
"failed to restore a FileStore file - missing Account Policies Sources")
}
func TestGetAccountByPrivateDomain(t *testing.T) {
storeDir := t.TempDir()

View File

@ -53,7 +53,6 @@ func (g *Group) Copy() *Group {
// GetGroup object of the peers
func (am *DefaultAccountManager) GetGroup(accountID, groupID string) (*Group, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -72,7 +71,6 @@ func (am *DefaultAccountManager) GetGroup(accountID, groupID string) (*Group, er
// SaveGroup object of the peers
func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *Group) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -112,8 +110,10 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G
continue
}
am.storeEvent(userID, peer.ID, accountID, activity.GroupAddedToPeer,
map[string]any{"group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
"peer_fqdn": peer.FQDN(am.GetDNSDomain())})
map[string]any{
"group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
"peer_fqdn": peer.FQDN(am.GetDNSDomain()),
})
}
for _, p := range removedPeers {
@ -123,8 +123,10 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G
continue
}
am.storeEvent(userID, peer.ID, accountID, activity.GroupRemovedFromPeer,
map[string]any{"group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
"peer_fqdn": peer.FQDN(am.GetDNSDomain())})
map[string]any{
"group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
"peer_fqdn": peer.FQDN(am.GetDNSDomain()),
})
}
return nil
@ -147,8 +149,8 @@ func difference(a, b []string) []string {
// UpdateGroup updates a group using a list of operations
func (am *DefaultAccountManager) UpdateGroup(accountID string,
groupID string, operations []GroupUpdateOperation) (*Group, error) {
groupID string, operations []GroupUpdateOperation,
) (*Group, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -198,7 +200,6 @@ func (am *DefaultAccountManager) UpdateGroup(accountID string,
// DeleteGroup object of the peers
func (am *DefaultAccountManager) DeleteGroup(accountID, groupID string) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -219,7 +220,6 @@ func (am *DefaultAccountManager) DeleteGroup(accountID, groupID string) error {
// ListGroups objects of the peers
func (am *DefaultAccountManager) ListGroups(accountID string) ([]*Group, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -238,7 +238,6 @@ func (am *DefaultAccountManager) ListGroups(accountID string) ([]*Group, error)
// GroupAddPeer appends peer to the group
func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -273,7 +272,6 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string)
// GroupDeletePeer removes peer from the group
func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey string) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -302,7 +300,6 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey str
// GroupListPeers returns list of the peers from the group
func (am *DefaultAccountManager) GroupListPeers(accountID, groupID string) ([]*Peer, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()

View File

@ -1,7 +1,7 @@
openapi: 3.0.1
info:
title: NetBird REST API
description: API to manipulate groups, rules and retrieve information about peers and users
description: API to manipulate groups, rules, policies and retrieve information about peers and users
version: 0.0.1
tags:
- name: Users
@ -14,6 +14,8 @@ tags:
description: Interact with and view information about groups.
- name: Rules
description: Interact with and view information about rules.
- name: Policies
description: Interact with and view information about policies.
- name: Routes
description: Interact with and view information about routes.
- name: DNS
@ -393,6 +395,77 @@ components:
enum: [ "name","description","disabled","flow","sources","destinations" ]
required:
- path
PolicyRule:
type: object
properties:
id:
description: Rule ID
type: string
name:
description: Rule name identifier
type: string
description:
description: Rule friendly description
type: string
enabled:
description: Rules status
type: boolean
sources:
description: policy source groups
type: array
items:
$ref: '#/components/schemas/GroupMinimum'
destinations:
description: policy destination groups
type: array
items:
$ref: '#/components/schemas/GroupMinimum'
action:
description: policy accept or drops packets
type: string
enum: ["accept","drop"]
required:
- name
- sources
- destinations
- action
- enabled
PolicyMinimum:
type: object
properties:
name:
description: Policy name identifier
type: string
description:
description: Policy friendly description
type: string
enabled:
description: Policy status
type: boolean
query:
description: Policy Rego query
type: string
rules:
description: Policy rule object for policy UI editor
type: array
items:
$ref: '#/components/schemas/PolicyRule'
required:
- name
- description
- enabled
- query
- rules
Policy:
allOf:
- $ref: '#/components/schemas/PolicyMinimum'
- type: object
properties:
id:
description: Policy ID
type: string
required:
- id
RouteRequest:
type: object
properties:
@ -574,12 +647,12 @@ components:
"setupkey.peer.add", "setupkey.add", "setupkey.update", "setupkey.revoke", "setupkey.overuse",
"setupkey.group.delete", "setupkey.group.add",
"rule.add", "rule.delete", "rule.update",
"policy.add", "policy.delete", "policy.update",
"group.add", "group.update", "dns.setting.disabled.management.group.add", "dns.setting.disabled.management.group.delete",
"account.create", "account.setting.peer.login.expiration.update", "account.setting.peer.login.expiration.disable", "account.setting.peer.login.expiration.enable",
"route.add", "route.delete", "route.update",
"nameserver.group.add", "nameserver.group.delete", "nameserver.group.update",
"peer.ssh.disable", "peer.ssh.enable", "peer.rename", "peer.login.expiration.disable", "peer.login.expiration.enable"
]
"peer.ssh.disable", "peer.ssh.enable", "peer.rename", "peer.login.expiration.disable", "peer.login.expiration.enable" ]
initiator_id:
description: The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
type: string
@ -1337,41 +1410,6 @@ paths:
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
patch:
summary: Update information about a Rule
tags: [ Rules ]
security:
- BearerAuth: [ ]
parameters:
- in: path
name: id
required: true
schema:
type: string
description: The Rule ID
requestBody:
description: Update Rule request using a list of json patch objects
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/RulePatchOperation'
responses:
'200':
description: A Rule object
content:
application/json:
schema:
$ref: '#/components/schemas/Rule'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Rule
tags: [ Rules ]
@ -1396,7 +1434,134 @@ paths:
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/policies:
get:
summary: Returns a list of all Policies
tags: [ Policies ]
security:
- BearerAuth: [ ]
responses:
'200':
description: A JSON Array of Policies
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Policy'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Creates a Policy
tags: [ Policies ]
security:
- BearerAuth: [ ]
requestBody:
description: New Policy request
content:
'application/json':
schema:
allOf:
- $ref: '#/components/schemas/PolicyMinimum'
responses:
'200':
description: A Policy Object
content:
application/json:
schema:
$ref: '#/components/schemas/Policy'
/api/policies/{id}:
get:
summary: Get information about a Policies
tags: [ Policies ]
security:
- BearerAuth: [ ]
parameters:
- in: path
name: id
required: true
schema:
type: string
description: The Policy ID
responses:
'200':
description: A Policy object
content:
application/json:
schema:
$ref: '#/components/schemas/Policy'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update/Replace a Policy
tags: [ Policies ]
security:
- BearerAuth: [ ]
parameters:
- in: path
name: id
required: true
schema:
type: string
description: The Policy ID
requestBody:
description: Update Policy request
content:
'application/json':
schema:
allOf:
- $ref: '#/components/schemas/PolicyMinimum'
responses:
'200':
description: A Policy object
content:
application/json:
schema:
$ref: '#/components/schemas/Policy'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Policy
tags: [ Policies ]
security:
- BearerAuth: [ ]
parameters:
- in: path
name: id
required: true
schema:
type: string
description: The Policy ID
responses:
'200':
description: Delete status code
content: { }
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/routes:
get:
summary: Returns a list of all routes

View File

@ -29,6 +29,9 @@ const (
EventActivityCodePeerRename EventActivityCode = "peer.rename"
EventActivityCodePeerSshDisable EventActivityCode = "peer.ssh.disable"
EventActivityCodePeerSshEnable EventActivityCode = "peer.ssh.enable"
EventActivityCodePolicyAdd EventActivityCode = "policy.add"
EventActivityCodePolicyDelete EventActivityCode = "policy.delete"
EventActivityCodePolicyUpdate EventActivityCode = "policy.update"
EventActivityCodeRouteAdd EventActivityCode = "route.add"
EventActivityCodeRouteDelete EventActivityCode = "route.delete"
EventActivityCodeRouteUpdate EventActivityCode = "route.update"
@ -94,6 +97,12 @@ const (
PatchMinimumOpReplace PatchMinimumOp = "replace"
)
// Defines values for PolicyRuleAction.
const (
PolicyRuleActionAccept PolicyRuleAction = "accept"
PolicyRuleActionDrop PolicyRuleAction = "drop"
)
// Defines values for RoutePatchOperationOp.
const (
RoutePatchOperationOpAdd RoutePatchOperationOp = "add"
@ -113,23 +122,6 @@ const (
RoutePatchOperationPathPeer RoutePatchOperationPath = "peer"
)
// Defines values for RulePatchOperationOp.
const (
RulePatchOperationOpAdd RulePatchOperationOp = "add"
RulePatchOperationOpRemove RulePatchOperationOp = "remove"
RulePatchOperationOpReplace RulePatchOperationOp = "replace"
)
// Defines values for RulePatchOperationPath.
const (
RulePatchOperationPathDescription RulePatchOperationPath = "description"
RulePatchOperationPathDestinations RulePatchOperationPath = "destinations"
RulePatchOperationPathDisabled RulePatchOperationPath = "disabled"
RulePatchOperationPathFlow RulePatchOperationPath = "flow"
RulePatchOperationPathName RulePatchOperationPath = "name"
RulePatchOperationPathSources RulePatchOperationPath = "sources"
)
// Defines values for UserStatus.
const (
UserStatusActive UserStatus = "active"
@ -387,6 +379,72 @@ type PeerMinimum struct {
Name string `json:"name"`
}
// Policy defines model for Policy.
type Policy struct {
// Description Policy friendly description
Description string `json:"description"`
// Enabled Policy status
Enabled bool `json:"enabled"`
// Id Policy ID
Id string `json:"id"`
// Name Policy name identifier
Name string `json:"name"`
// Query Policy Rego query
Query string `json:"query"`
// Rules Policy rule object for policy UI editor
Rules []PolicyRule `json:"rules"`
}
// PolicyMinimum defines model for PolicyMinimum.
type PolicyMinimum struct {
// Description Policy friendly description
Description string `json:"description"`
// Enabled Policy status
Enabled bool `json:"enabled"`
// Name Policy name identifier
Name string `json:"name"`
// Query Policy Rego query
Query string `json:"query"`
// Rules Policy rule object for policy UI editor
Rules []PolicyRule `json:"rules"`
}
// PolicyRule defines model for PolicyRule.
type PolicyRule struct {
// Action policy accept or drops packets
Action PolicyRuleAction `json:"action"`
// Description Rule friendly description
Description *string `json:"description,omitempty"`
// Destinations policy destination groups
Destinations []GroupMinimum `json:"destinations"`
// Enabled Rules status
Enabled bool `json:"enabled"`
// Id Rule ID
Id *string `json:"id,omitempty"`
// Name Rule name identifier
Name string `json:"name"`
// Sources policy source groups
Sources []GroupMinimum `json:"sources"`
}
// PolicyRuleAction policy accept or drops packets
type PolicyRuleAction string
// Route defines model for Route.
type Route struct {
// Description Route description
@ -504,24 +562,6 @@ type RuleMinimum struct {
Name string `json:"name"`
}
// RulePatchOperation defines model for RulePatchOperation.
type RulePatchOperation struct {
// Op Patch operation type
Op RulePatchOperationOp `json:"op"`
// Path Rule field to update in form /<field>
Path RulePatchOperationPath `json:"path"`
// Value Values to be applied
Value []string `json:"value"`
}
// RulePatchOperationOp Patch operation type
type RulePatchOperationOp string
// RulePatchOperationPath Rule field to update in form /<field>
type RulePatchOperationPath string
// SetupKey defines model for SetupKey.
type SetupKey struct {
// AutoGroups Setup key groups to auto-assign to peers registered with this key
@ -666,6 +706,12 @@ type PutApiPeersIdJSONBody struct {
SshEnabled bool `json:"ssh_enabled"`
}
// PostApiPoliciesJSONBody defines parameters for PostApiPolicies.
type PostApiPoliciesJSONBody = PolicyMinimum
// PutApiPoliciesIdJSONBody defines parameters for PutApiPoliciesId.
type PutApiPoliciesIdJSONBody = PolicyMinimum
// PatchApiRoutesIdJSONBody defines parameters for PatchApiRoutesId.
type PatchApiRoutesIdJSONBody = []RoutePatchOperation
@ -686,9 +732,6 @@ type PostApiRulesJSONBody struct {
Sources *[]string `json:"sources,omitempty"`
}
// PatchApiRulesIdJSONBody defines parameters for PatchApiRulesId.
type PatchApiRulesIdJSONBody = []RulePatchOperation
// PutApiRulesIdJSONBody defines parameters for PutApiRulesId.
type PutApiRulesIdJSONBody struct {
// Description Rule friendly description
@ -733,6 +776,12 @@ type PutApiGroupsIdJSONRequestBody PutApiGroupsIdJSONBody
// PutApiPeersIdJSONRequestBody defines body for PutApiPeersId for application/json ContentType.
type PutApiPeersIdJSONRequestBody PutApiPeersIdJSONBody
// PostApiPoliciesJSONRequestBody defines body for PostApiPolicies for application/json ContentType.
type PostApiPoliciesJSONRequestBody = PostApiPoliciesJSONBody
// PutApiPoliciesIdJSONRequestBody defines body for PutApiPoliciesId for application/json ContentType.
type PutApiPoliciesIdJSONRequestBody = PutApiPoliciesIdJSONBody
// PostApiRoutesJSONRequestBody defines body for PostApiRoutes for application/json ContentType.
type PostApiRoutesJSONRequestBody = RouteRequest
@ -745,9 +794,6 @@ type PutApiRoutesIdJSONRequestBody = RouteRequest
// PostApiRulesJSONRequestBody defines body for PostApiRules for application/json ContentType.
type PostApiRulesJSONRequestBody PostApiRulesJSONBody
// PatchApiRulesIdJSONRequestBody defines body for PatchApiRulesId for application/json ContentType.
type PatchApiRulesIdJSONRequestBody = PatchApiRulesIdJSONBody
// PutApiRulesIdJSONRequestBody defines body for PutApiRulesId for application/json ContentType.
type PutApiRulesIdJSONRequestBody PutApiRulesIdJSONBody

View File

@ -59,6 +59,7 @@ func APIHandler(accountManager s.AccountManager, appMetrics telemetry.AppMetrics
api.addUsersEndpoint()
api.addSetupKeysEndpoint()
api.addRulesEndpoint()
api.addPoliciesEndpoint()
api.addGroupsEndpoint()
api.addRoutesEndpoint()
api.addDNSNameserversEndpoint()
@ -122,11 +123,19 @@ func (apiHandler *apiHandler) addRulesEndpoint() {
apiHandler.Router.HandleFunc("/rules", rulesHandler.GetAllRules).Methods("GET", "OPTIONS")
apiHandler.Router.HandleFunc("/rules", rulesHandler.CreateRule).Methods("POST", "OPTIONS")
apiHandler.Router.HandleFunc("/rules/{id}", rulesHandler.UpdateRule).Methods("PUT", "OPTIONS")
apiHandler.Router.HandleFunc("/rules/{id}", rulesHandler.PatchRule).Methods("PATCH", "OPTIONS")
apiHandler.Router.HandleFunc("/rules/{id}", rulesHandler.GetRule).Methods("GET", "OPTIONS")
apiHandler.Router.HandleFunc("/rules/{id}", rulesHandler.DeleteRule).Methods("DELETE", "OPTIONS")
}
func (apiHandler *apiHandler) addPoliciesEndpoint() {
policiesHandler := NewPoliciesHandler(apiHandler.AccountManager, apiHandler.AuthCfg)
apiHandler.Router.HandleFunc("/policies", policiesHandler.GetAllPolicies).Methods("GET", "OPTIONS")
apiHandler.Router.HandleFunc("/policies", policiesHandler.CreatePolicy).Methods("POST", "OPTIONS")
apiHandler.Router.HandleFunc("/policies/{id}", policiesHandler.UpdatePolicy).Methods("PUT", "OPTIONS")
apiHandler.Router.HandleFunc("/policies/{id}", policiesHandler.GetPolicy).Methods("GET", "OPTIONS")
apiHandler.Router.HandleFunc("/policies/{id}", policiesHandler.DeletePolicy).Methods("DELETE", "OPTIONS")
}
func (apiHandler *apiHandler) addGroupsEndpoint() {
groupsHandler := NewGroupsHandler(apiHandler.AccountManager, apiHandler.AuthCfg)
apiHandler.Router.HandleFunc("/groups", groupsHandler.GetAllGroups).Methods("GET", "OPTIONS")

View File

@ -0,0 +1,326 @@
package http
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/netbirdio/netbird/management/server/status"
)
// Policies is a handler that returns policy of the account
type Policies struct {
accountManager server.AccountManager
claimsExtractor *jwtclaims.ClaimsExtractor
}
// NewPoliciesHandler creates a new Policies handler
func NewPoliciesHandler(accountManager server.AccountManager, authCfg AuthCfg) *Policies {
return &Policies{
accountManager: accountManager,
claimsExtractor: jwtclaims.NewClaimsExtractor(
jwtclaims.WithAudience(authCfg.Audience),
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
),
}
}
// GetAllPolicies list for the account
func (h *Policies) GetAllPolicies(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
accountPolicies, err := h.accountManager.ListPolicies(account.Id, user.Id)
if err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, accountPolicies)
}
// UpdatePolicy handles update to a policy identified by a given ID
func (h *Policies) UpdatePolicy(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
vars := mux.Vars(r)
policyID := vars["id"]
if len(policyID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid policy ID"), w)
return
}
policyIdx := -1
for i, policy := range account.Policies {
if policy.ID == policyID {
policyIdx = i
break
}
}
if policyIdx < 0 {
util.WriteError(status.Errorf(status.NotFound, "couldn't find policy id %s", policyID), w)
return
}
var req api.PutApiPoliciesIdJSONRequestBody
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
return
}
if req.Name == "" {
util.WriteError(status.Errorf(status.InvalidArgument, "policy name shouldn't be empty"), w)
return
}
policy := server.Policy{
ID: policyID,
Name: req.Name,
Enabled: req.Enabled,
Description: req.Description,
Query: req.Query,
}
if req.Rules != nil {
for _, r := range req.Rules {
pr := server.PolicyRule{
Destinations: groupMinimumsToStrings(account, r.Destinations),
Sources: groupMinimumsToStrings(account, r.Sources),
Name: r.Name,
}
pr.Enabled = r.Enabled
if r.Description != nil {
pr.Description = *r.Description
}
if r.Id != nil {
pr.ID = *r.Id
}
switch r.Action {
case api.PolicyRuleActionAccept:
pr.Action = server.PolicyTrafficActionAccept
case api.PolicyRuleActionDrop:
pr.Action = server.PolicyTrafficActionDrop
default:
util.WriteError(status.Errorf(status.InvalidArgument, "unknown action type"), w)
return
}
policy.Rules = append(policy.Rules, &pr)
}
}
if err := policy.UpdateQueryFromRules(); err != nil {
log.Errorf("failed to update policy query: %v", err)
util.WriteError(err, w)
return
}
if err = h.accountManager.SavePolicy(account.Id, user.Id, &policy); err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, toPolicyResponse(account, &policy))
}
// CreatePolicy handles policy creation request
func (h *Policies) CreatePolicy(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
var req api.PostApiPoliciesJSONRequestBody
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
return
}
if req.Name == "" {
util.WriteError(status.Errorf(status.InvalidArgument, "policy name shouldn't be empty"), w)
return
}
policy := &server.Policy{
ID: xid.New().String(),
Name: req.Name,
Enabled: req.Enabled,
Description: req.Description,
Query: req.Query,
}
if req.Rules != nil {
for _, r := range req.Rules {
pr := server.PolicyRule{
ID: xid.New().String(),
Destinations: groupMinimumsToStrings(account, r.Destinations),
Sources: groupMinimumsToStrings(account, r.Sources),
Name: r.Name,
}
pr.Enabled = r.Enabled
if r.Description != nil {
pr.Description = *r.Description
}
switch r.Action {
case api.PolicyRuleActionAccept:
pr.Action = server.PolicyTrafficActionAccept
case api.PolicyRuleActionDrop:
pr.Action = server.PolicyTrafficActionDrop
default:
util.WriteError(status.Errorf(status.InvalidArgument, "unknown action type"), w)
return
}
policy.Rules = append(policy.Rules, &pr)
}
}
if err := policy.UpdateQueryFromRules(); err != nil {
util.WriteError(err, w)
return
}
if err = h.accountManager.SavePolicy(account.Id, user.Id, policy); err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, toPolicyResponse(account, policy))
}
// DeletePolicy handles policy deletion request
func (h *Policies) DeletePolicy(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
aID := account.Id
vars := mux.Vars(r)
policyID := vars["id"]
if len(policyID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid policy ID"), w)
return
}
if err = h.accountManager.DeletePolicy(aID, policyID, user.Id); err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, "")
}
// GetPolicy handles a group Get request identified by ID
func (h *Policies) GetPolicy(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
switch r.Method {
case http.MethodGet:
vars := mux.Vars(r)
policyID := vars["id"]
if len(policyID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid policy ID"), w)
return
}
policy, err := h.accountManager.GetPolicy(account.Id, policyID, user.Id)
if err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, toPolicyResponse(account, policy))
default:
util.WriteError(status.Errorf(status.NotFound, "method not found"), w)
}
}
func toPolicyResponse(account *server.Account, policy *server.Policy) *api.Policy {
cache := make(map[string]api.GroupMinimum)
ap := &api.Policy{
Id: policy.ID,
Name: policy.Name,
Description: policy.Description,
Enabled: policy.Enabled,
Query: policy.Query,
}
if len(policy.Rules) == 0 {
return ap
}
for _, r := range policy.Rules {
rule := api.PolicyRule{
Id: &r.ID,
Name: r.Name,
Enabled: r.Enabled,
Description: &r.Description,
}
for _, gid := range r.Sources {
_, ok := cache[gid]
if ok {
continue
}
if group, ok := account.Groups[gid]; ok {
minimum := api.GroupMinimum{
Id: group.ID,
Name: group.Name,
PeersCount: len(group.Peers),
}
rule.Sources = append(rule.Sources, minimum)
cache[gid] = minimum
}
}
for _, gid := range r.Destinations {
cachedMinimum, ok := cache[gid]
if ok {
rule.Destinations = append(rule.Destinations, cachedMinimum)
continue
}
if group, ok := account.Groups[gid]; ok {
minimum := api.GroupMinimum{
Id: group.ID,
Name: group.Name,
PeersCount: len(group.Peers),
}
rule.Destinations = append(rule.Destinations, minimum)
cache[gid] = minimum
}
}
ap.Rules = append(ap.Rules, rule)
}
return ap
}
func groupMinimumsToStrings(account *server.Account, gm []api.GroupMinimum) []string {
result := make([]string, 0, len(gm))
for _, gm := range gm {
if _, ok := account.Groups[gm.Id]; ok {
continue
}
result = append(result, gm.Id)
}
return result
}

View File

@ -40,14 +40,16 @@ func (h *RulesHandler) GetAllRules(w http.ResponseWriter, r *http.Request) {
return
}
accountRules, err := h.accountManager.ListRules(account.Id, user.Id)
accountPolicies, err := h.accountManager.ListPolicies(account.Id, user.Id)
if err != nil {
util.WriteError(err, w)
return
}
rules := []*api.Rule{}
for _, r := range accountRules {
rules = append(rules, toRuleResponse(account, r))
for _, policy := range accountPolicies {
for _, r := range policy.Rules {
rules = append(rules, toRuleResponse(account, r.ToRule()))
}
}
util.WriteJSONObject(w, rules)
@ -69,9 +71,9 @@ func (h *RulesHandler) UpdateRule(w http.ResponseWriter, r *http.Request) {
return
}
_, ok := account.Rules[ruleID]
if !ok {
util.WriteError(status.Errorf(status.NotFound, "couldn't find rule id %s", ruleID), w)
policy, err := h.accountManager.GetPolicy(account.Id, ruleID, user.Id)
if err != nil {
util.WriteError(err, w)
return
}
@ -96,173 +98,40 @@ func (h *RulesHandler) UpdateRule(w http.ResponseWriter, r *http.Request) {
reqDestinations = *req.Destinations
}
rule := server.Rule{
ID: ruleID,
Name: req.Name,
Source: reqSources,
Destination: reqDestinations,
Disabled: req.Disabled,
Description: req.Description,
if len(policy.Rules) != 1 {
util.WriteError(status.Errorf(status.Internal, "policy should contain exactly one rule"), w)
return
}
policy.Name = req.Name
policy.Description = req.Description
policy.Enabled = !req.Disabled
policy.Rules[0].ID = ruleID
policy.Rules[0].Name = req.Name
policy.Rules[0].Sources = reqSources
policy.Rules[0].Destinations = reqDestinations
policy.Rules[0].Enabled = !req.Disabled
policy.Rules[0].Description = req.Description
if err := policy.UpdateQueryFromRules(); err != nil {
util.WriteError(err, w)
return
}
switch req.Flow {
case server.TrafficFlowBidirectString:
rule.Flow = server.TrafficFlowBidirect
policy.Rules[0].Action = server.PolicyTrafficActionAccept
default:
util.WriteError(status.Errorf(status.InvalidArgument, "unknown flow type"), w)
return
}
err = h.accountManager.SaveRule(account.Id, user.Id, &rule)
err = h.accountManager.SavePolicy(account.Id, user.Id, policy)
if err != nil {
util.WriteError(err, w)
return
}
resp := toRuleResponse(account, &rule)
util.WriteJSONObject(w, &resp)
}
// PatchRule handles patch updates to a rule identified by a given ID
func (h *RulesHandler) PatchRule(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, _, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
vars := mux.Vars(r)
ruleID := vars["id"]
if len(ruleID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid rule ID"), w)
return
}
_, ok := account.Rules[ruleID]
if !ok {
util.WriteError(status.Errorf(status.NotFound, "couldn't find rule ID %s", ruleID), w)
return
}
var req api.PatchApiRulesIdJSONRequestBody
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
return
}
if len(req) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "no patch instruction received"), w)
return
}
var operations []server.RuleUpdateOperation
for _, patch := range req {
switch patch.Path {
case api.RulePatchOperationPathName:
if patch.Op != api.RulePatchOperationOpReplace {
util.WriteError(status.Errorf(status.InvalidArgument,
"name field only accepts replace operation, got %s", patch.Op), w)
return
}
if len(patch.Value) == 0 || patch.Value[0] == "" {
util.WriteError(status.Errorf(status.InvalidArgument, "rule name shouldn't be empty"), w)
return
}
operations = append(operations, server.RuleUpdateOperation{
Type: server.UpdateRuleName,
Values: patch.Value,
})
case api.RulePatchOperationPathDescription:
if patch.Op != api.RulePatchOperationOpReplace {
util.WriteError(status.Errorf(status.InvalidArgument,
"description field only accepts replace operation, got %s", patch.Op), w)
return
}
operations = append(operations, server.RuleUpdateOperation{
Type: server.UpdateRuleDescription,
Values: patch.Value,
})
case api.RulePatchOperationPathFlow:
if patch.Op != api.RulePatchOperationOpReplace {
util.WriteError(status.Errorf(status.InvalidArgument,
"flow field only accepts replace operation, got %s", patch.Op), w)
return
}
operations = append(operations, server.RuleUpdateOperation{
Type: server.UpdateRuleFlow,
Values: patch.Value,
})
case api.RulePatchOperationPathDisabled:
if patch.Op != api.RulePatchOperationOpReplace {
util.WriteError(status.Errorf(status.InvalidArgument,
"disabled field only accepts replace operation, got %s", patch.Op), w)
return
}
operations = append(operations, server.RuleUpdateOperation{
Type: server.UpdateRuleStatus,
Values: patch.Value,
})
case api.RulePatchOperationPathSources:
switch patch.Op {
case api.RulePatchOperationOpReplace:
operations = append(operations, server.RuleUpdateOperation{
Type: server.UpdateSourceGroups,
Values: patch.Value,
})
case api.RulePatchOperationOpRemove:
operations = append(operations, server.RuleUpdateOperation{
Type: server.RemoveGroupsFromSource,
Values: patch.Value,
})
case api.RulePatchOperationOpAdd:
operations = append(operations, server.RuleUpdateOperation{
Type: server.InsertGroupsToSource,
Values: patch.Value,
})
default:
util.WriteError(status.Errorf(status.InvalidArgument,
"invalid operation \"%s\" on Source field", patch.Op), w)
return
}
case api.RulePatchOperationPathDestinations:
switch patch.Op {
case api.RulePatchOperationOpReplace:
operations = append(operations, server.RuleUpdateOperation{
Type: server.UpdateDestinationGroups,
Values: patch.Value,
})
case api.RulePatchOperationOpRemove:
operations = append(operations, server.RuleUpdateOperation{
Type: server.RemoveGroupsFromDestination,
Values: patch.Value,
})
case api.RulePatchOperationOpAdd:
operations = append(operations, server.RuleUpdateOperation{
Type: server.InsertGroupsToDestination,
Values: patch.Value,
})
default:
util.WriteError(status.Errorf(status.InvalidArgument,
"invalid operation \"%s\" on Destination field", patch.Op), w)
return
}
default:
util.WriteError(status.Errorf(status.InvalidArgument, "invalid patch path"), w)
return
}
}
rule, err := h.accountManager.UpdateRule(account.Id, ruleID, operations)
if err != nil {
util.WriteError(err, w)
return
}
resp := toRuleResponse(account, rule)
resp := toRuleResponse(account, policy.Rules[0].ToRule())
util.WriteJSONObject(w, &resp)
}
@ -315,7 +184,12 @@ func (h *RulesHandler) CreateRule(w http.ResponseWriter, r *http.Request) {
return
}
err = h.accountManager.SaveRule(account.Id, user.Id, &rule)
policy, err := server.RuleToPolicy(&rule)
if err != nil {
util.WriteError(err, w)
return
}
err = h.accountManager.SavePolicy(account.Id, user.Id, policy)
if err != nil {
util.WriteError(err, w)
return
@ -342,7 +216,7 @@ func (h *RulesHandler) DeleteRule(w http.ResponseWriter, r *http.Request) {
return
}
err = h.accountManager.DeleteRule(aID, rID, user.Id)
err = h.accountManager.DeletePolicy(aID, rID, user.Id)
if err != nil {
util.WriteError(err, w)
return
@ -368,13 +242,13 @@ func (h *RulesHandler) GetRule(w http.ResponseWriter, r *http.Request) {
return
}
rule, err := h.accountManager.GetRule(account.Id, ruleID, user.Id)
policy, err := h.accountManager.GetPolicy(account.Id, ruleID, user.Id)
if err != nil {
util.WriteError(status.Errorf(status.NotFound, "rule not found"), w)
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, toRuleResponse(account, rule))
util.WriteJSONObject(w, toRuleResponse(account, policy.Rules[0].ToRule()))
default:
util.WriteError(status.Errorf(status.NotFound, "method not found"), w)
}

View File

@ -11,6 +11,7 @@ import (
"testing"
"github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/status"
"github.com/gorilla/mux"
@ -23,8 +24,32 @@ import (
)
func initRulesTestData(rules ...*server.Rule) *RulesHandler {
testPolicies := make(map[string]*server.Policy, len(rules))
for _, rule := range rules {
policy, err := server.RuleToPolicy(rule)
if err != nil {
panic(err)
}
if err := policy.UpdateQueryFromRules(); err != nil {
panic(err)
}
testPolicies[policy.ID] = policy
}
return &RulesHandler{
accountManager: &mock_server.MockAccountManager{
GetPolicyFunc: func(_, policyID, _ string) (*server.Policy, error) {
policy, ok := testPolicies[policyID]
if !ok {
return nil, status.Errorf(status.NotFound, "policy not found")
}
return policy, nil
},
SavePolicyFunc: func(_, _ string, policy *server.Policy) error {
if !strings.HasPrefix(policy.ID, "id-") {
policy.ID = "id-was-set"
}
return nil
},
SaveRuleFunc: func(_, _ string, rule *server.Rule) error {
if !strings.HasPrefix(rule.ID, "id-") {
rule.ID = "id-was-set"
@ -43,32 +68,6 @@ func initRulesTestData(rules ...*server.Rule) *RulesHandler {
Flow: server.TrafficFlowBidirect,
}, nil
},
UpdateRuleFunc: func(_ string, ruleID string, operations []server.RuleUpdateOperation) (*server.Rule, error) {
var rule server.Rule
rule.ID = ruleID
for _, operation := range operations {
switch operation.Type {
case server.UpdateRuleName:
rule.Name = operation.Values[0]
case server.UpdateRuleDescription:
rule.Description = operation.Values[0]
case server.UpdateRuleFlow:
if server.TrafficFlowBidirectString == operation.Values[0] {
rule.Flow = server.TrafficFlowBidirect
} else {
rule.Flow = 100
}
case server.UpdateSourceGroups, server.InsertGroupsToSource:
rule.Source = operation.Values
case server.UpdateDestinationGroups, server.InsertGroupsToDestination:
rule.Destination = operation.Values
case server.RemoveGroupsFromSource, server.RemoveGroupsFromDestination:
default:
return nil, fmt.Errorf("no operation")
}
}
return &rule, nil
},
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
user := server.NewAdminUser("test_user")
return &server.Account{
@ -221,58 +220,13 @@ func TestRulesWriteRule(t *testing.T) {
[]byte(`{"Name":"","Flow":"bidirect"}`)),
expectedStatus: http.StatusUnprocessableEntity,
},
{
name: "Write Rule PATCH Name OK",
requestType: http.MethodPatch,
requestPath: "/api/rules/id-existed",
requestBody: bytes.NewBuffer(
[]byte(`[{"op":"replace","path":"name","value":["Default POSTed Rule"]}]`)),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedRule: &api.Rule{
Id: "id-existed",
Name: "Default POSTed Rule",
Flow: server.TrafficFlowBidirectString,
},
},
{
name: "Write Rule PATCH Invalid Name OP",
requestType: http.MethodPatch,
requestPath: "/api/rules/id-existed",
requestBody: bytes.NewBuffer(
[]byte(`[{"op":"insert","path":"name","value":[""]}]`)),
expectedStatus: http.StatusUnprocessableEntity,
expectedBody: false,
},
{
name: "Write Rule PATCH Invalid Name",
requestType: http.MethodPatch,
requestPath: "/api/rules/id-existed",
requestBody: bytes.NewBuffer(
[]byte(`[{"op":"replace","path":"name","value":[]}]`)),
expectedStatus: http.StatusUnprocessableEntity,
expectedBody: false,
},
{
name: "Write Rule PATCH Sources OK",
requestType: http.MethodPatch,
requestPath: "/api/rules/id-existed",
requestBody: bytes.NewBuffer(
[]byte(`[{"op":"replace","path":"sources","value":["G","F"]}]`)),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedRule: &api.Rule{
Id: "id-existed",
Flow: server.TrafficFlowBidirectString,
Sources: []api.GroupMinimum{
{Id: "G"},
{Id: "F"},
},
},
},
}
p := initRulesTestData()
p := initRulesTestData(&server.Rule{
ID: "id-existed",
Name: "Default POSTed Rule",
Flow: server.TrafficFlowBidirect,
})
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
@ -282,7 +236,6 @@ func TestRulesWriteRule(t *testing.T) {
router := mux.NewRouter()
router.HandleFunc("/api/rules", p.CreateRule).Methods("POST")
router.HandleFunc("/api/rules/{id}", p.UpdateRule).Methods("PUT")
router.HandleFunc("/api/rules/{id}", p.PatchRule).Methods("PATCH")
router.ServeHTTP(recorder, req)
res := recorder.Result()
@ -307,6 +260,7 @@ func TestRulesWriteRule(t *testing.T) {
if err = json.Unmarshal(content, &got); err != nil {
t.Fatalf("Sent content is not in correct json format; %v", err)
}
tc.expectedRule.Id = got.Id
assert.Equal(t, got, tc.expectedRule)
})

View File

@ -2,7 +2,6 @@ package server_test
import (
"context"
"github.com/netbirdio/netbird/management/server/activity"
"math/rand"
"net"
"os"
@ -11,6 +10,8 @@ import (
sync2 "sync"
"time"
"github.com/netbirdio/netbird/management/server/activity"
server "github.com/netbirdio/netbird/management/server"
"google.golang.org/grpc/credentials/insecure"
@ -29,6 +30,7 @@ import (
const (
ValidSetupKey = "A2C8E62B-38F5-4553-B31E-DD66C696CEBB"
AccountKey = "bf1c8084-ba50-4ce7-9439-34653001fc3b"
)
var _ = Describe("Management service", func() {

View File

@ -40,9 +40,12 @@ type MockAccountManager struct {
GroupListPeersFunc func(accountID, groupID string) ([]*server.Peer, error)
GetRuleFunc func(accountID, ruleID, userID string) (*server.Rule, error)
SaveRuleFunc func(accountID, userID string, rule *server.Rule) error
UpdateRuleFunc func(accountID string, ruleID string, operations []server.RuleUpdateOperation) (*server.Rule, error)
DeleteRuleFunc func(accountID, ruleID, userID string) error
ListRulesFunc func(accountID, userID string) ([]*server.Rule, error)
GetPolicyFunc func(accountID, policyID, userID string) (*server.Policy, error)
SavePolicyFunc func(accountID, userID string, policy *server.Policy) error
DeletePolicyFunc func(accountID, policyID, userID string) error
ListPoliciesFunc func(accountID, userID string) ([]*server.Policy, error)
GetUsersFromAccountFunc func(accountID, userID string) ([]*server.UserInfo, error)
UpdatePeerMetaFunc func(peerID string, meta server.PeerSystemMeta) error
UpdatePeerSSHKeyFunc func(peerID string, sshKey string) error
@ -280,14 +283,6 @@ func (am *MockAccountManager) SaveRule(accountID, userID string, rule *server.Ru
return status.Errorf(codes.Unimplemented, "method SaveRule is not implemented")
}
// UpdateRule mock implementation of UpdateRule from server.AccountManager interface
func (am *MockAccountManager) UpdateRule(accountID string, ruleID string, operations []server.RuleUpdateOperation) (*server.Rule, error) {
if am.UpdateRuleFunc != nil {
return am.UpdateRuleFunc(accountID, ruleID, operations)
}
return nil, status.Errorf(codes.Unimplemented, "method UpdateRule not implemented")
}
// DeleteRule mock implementation of DeleteRule from server.AccountManager interface
func (am *MockAccountManager) DeleteRule(accountID, ruleID, userID string) error {
if am.DeleteRuleFunc != nil {
@ -304,6 +299,38 @@ func (am *MockAccountManager) ListRules(accountID, userID string) ([]*server.Rul
return nil, status.Errorf(codes.Unimplemented, "method ListRules is not implemented")
}
// GetPolicy mock implementation of GetPolicy from server.AccountManager interface
func (am *MockAccountManager) GetPolicy(accountID, policyID, userID string) (*server.Policy, error) {
if am.GetPolicyFunc != nil {
return am.GetPolicyFunc(accountID, policyID, userID)
}
return nil, status.Errorf(codes.Unimplemented, "method GetPolicy is not implemented")
}
// SavePolicy mock implementation of SavePolicy from server.AccountManager interface
func (am *MockAccountManager) SavePolicy(accountID, userID string, policy *server.Policy) error {
if am.SavePolicyFunc != nil {
return am.SavePolicyFunc(accountID, userID, policy)
}
return status.Errorf(codes.Unimplemented, "method SavePolicy is not implemented")
}
// DeletePolicy mock implementation of DeletePolicy from server.AccountManager interface
func (am *MockAccountManager) DeletePolicy(accountID, policyID, userID string) error {
if am.DeletePolicyFunc != nil {
return am.DeletePolicyFunc(accountID, policyID, userID)
}
return status.Errorf(codes.Unimplemented, "method DeletePolicy is not implemented")
}
// ListPolicies mock implementation of ListPolicies from server.AccountManager interface
func (am *MockAccountManager) ListPolicies(accountID, userID string) ([]*server.Policy, error) {
if am.ListPoliciesFunc != nil {
return am.ListPoliciesFunc(accountID, userID)
}
return nil, status.Errorf(codes.Unimplemented, "method ListPolicies is not implemented")
}
// UpdatePeerMeta mock implementation of UpdatePeerMeta from server.AccountManager interface
func (am *MockAccountManager) UpdatePeerMeta(peerID string, meta server.PeerSystemMeta) error {
if am.UpdatePeerMetaFunc != nil {
@ -477,7 +504,8 @@ func (am *MockAccountManager) CreateUser(accountID, userID string, invite *serve
// GetAccountFromToken mocks GetAccountFromToken of the AccountManager interface
func (am *MockAccountManager) GetAccountFromToken(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User,
error) {
error,
) {
if am.GetAccountFromTokenFunc != nil {
return am.GetAccountFromTokenFunc(claims)
}

View File

@ -2,13 +2,14 @@ package server
import (
"fmt"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/status"
"github.com/rs/xid"
"net"
"strings"
"time"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/status"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/proto"
@ -171,7 +172,6 @@ func (p *PeerStatus) Copy() *PeerStatus {
// GetPeerByKey looks up peer by its public WireGuard key
func (am *DefaultAccountManager) GetPeerByKey(peerPubKey string) (*Peer, error) {
account, err := am.Store.GetAccountByPeerPubKey(peerPubKey)
if err != nil {
return nil, err
@ -183,7 +183,6 @@ func (am *DefaultAccountManager) GetPeerByKey(peerPubKey string) (*Peer, error)
// GetPeers returns a list of peers under the given account filtering out peers that do not belong to a user if
// the current user is not an admin.
func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, error) {
account, err := am.Store.GetAccount(accountID)
if err != nil {
return nil, err
@ -208,7 +207,8 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er
// fetch all the peers that have access to the user's peers
for _, peer := range peers {
aclPeers := account.getPeersByACL(peer.ID)
// TODO: use firewall rules
aclPeers, _ := account.getPeersByPolicy(peer.ID)
for _, p := range aclPeers {
peersMap[p.ID] = p
}
@ -224,7 +224,6 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er
// MarkPeerConnected marks peer as connected (true) or disconnected (false)
func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected bool) error {
account, err := am.Store.GetAccountByPeerPubKey(peerPubKey)
if err != nil {
return err
@ -266,7 +265,7 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected
if oldStatus.LoginExpired {
// we need to update other peers because when peer login expires all other peers are notified to disconnect from
//the expired one. Here we notify them that connection is now allowed again.
// the expired one. Here we notify them that connection is now allowed again.
err = am.updateAccountPeers(account)
if err != nil {
return err
@ -278,7 +277,6 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected
// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, and Peer.LoginExpirationEnabled can be updated.
func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Peer) (*Peer, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -352,7 +350,6 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Pe
// DeletePeer removes peer from the account by its IP
func (am *DefaultAccountManager) DeletePeer(accountID, peerID, userID string) (*Peer, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -402,7 +399,6 @@ func (am *DefaultAccountManager) DeletePeer(accountID, peerID, userID string) (*
// GetPeerByIP returns peer by its IP
func (am *DefaultAccountManager) GetPeerByIP(accountID string, peerIP string) (*Peer, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -422,7 +418,6 @@ func (am *DefaultAccountManager) GetPeerByIP(accountID string, peerIP string) (*
// GetNetworkMap returns Network map for a given peer (omits original peer from the Peers result)
func (am *DefaultAccountManager) GetNetworkMap(peerID string) (*NetworkMap, error) {
account, err := am.Store.GetAccountByPeerID(peerID)
if err != nil {
return nil, err
@ -437,7 +432,6 @@ func (am *DefaultAccountManager) GetNetworkMap(peerID string) (*NetworkMap, erro
// GetPeerNetwork returns the Network for a given peer
func (am *DefaultAccountManager) GetPeerNetwork(peerID string) (*Network, error) {
account, err := am.Store.GetAccountByPeerID(peerID)
if err != nil {
return nil, err
@ -619,7 +613,6 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*Peer, *NetworkMap, er
// LoginPeer logs in or registers a peer.
// If peer doesn't exist the function checks whether a setup key or a user is present and registers a new peer if so.
func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) {
account, err := am.Store.GetAccountByPeerPubKey(login.WireGuardPubKey)
if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound {
@ -680,7 +673,6 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap,
}
}
return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil
}
func checkAuth(loginUserID string, peer *Peer) error {
@ -749,7 +741,6 @@ func (am *DefaultAccountManager) checkAndUpdatePeerSSHKey(peer *Peer, account *A
// UpdatePeerSSHKey updates peer's public SSH key
func (am *DefaultAccountManager) UpdatePeerSSHKey(peerID string, sshKey string) error {
if sshKey == "" {
log.Debugf("empty SSH key provided for peer %s, skipping update", peerID)
return nil
@ -793,7 +784,6 @@ func (am *DefaultAccountManager) UpdatePeerSSHKey(peerID string, sshKey string)
// GetPeer for a given accountID, peerID and userID error if not found.
func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*Peer, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
@ -825,7 +815,7 @@ func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*Pee
}
for _, p := range userPeers {
aclPeers := account.getPeersByACL(p.ID)
aclPeers, _ := account.getPeersByPolicy(p.ID)
for _, aclPeer := range aclPeers {
if aclPeer.ID == peerID {
return peer, nil
@ -842,62 +832,6 @@ func updatePeerMeta(peer *Peer, meta PeerSystemMeta, account *Account) *Peer {
return peer
}
// getPeersByACL returns all peers that given peer has access to.
func (a *Account) getPeersByACL(peerID string) []*Peer {
var peers []*Peer
srcRules, dstRules := a.GetPeerRules(peerID)
groups := map[string]*Group{}
for _, r := range srcRules {
if r.Disabled {
continue
}
if r.Flow == TrafficFlowBidirect {
for _, gid := range r.Destination {
if group, ok := a.Groups[gid]; ok {
groups[gid] = group
}
}
}
}
for _, r := range dstRules {
if r.Disabled {
continue
}
if r.Flow == TrafficFlowBidirect {
for _, gid := range r.Source {
if group, ok := a.Groups[gid]; ok {
groups[gid] = group
}
}
}
}
peersSet := make(map[string]struct{})
for _, g := range groups {
for _, pid := range g.Peers {
peer, ok := a.Peers[pid]
if !ok {
log.Warnf(
"peer %s found in group %s but doesn't belong to account %s",
pid,
g.ID,
a.Id,
)
continue
}
// exclude original peer
if _, ok := peersSet[peer.ID]; peer.ID != peerID && !ok {
peersSet[peer.ID] = struct{}{}
peers = append(peers, peer.Copy())
}
}
}
return peers
}
// updateAccountPeers updates all peers that belong to an account.
// Should be called when changes have to be synced to peers.
func (am *DefaultAccountManager) updateAccountPeers(account *Account) error {

View File

@ -1,16 +1,16 @@
package server
import (
"github.com/stretchr/testify/assert"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/rs/xid"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
func TestPeer_LoginExpired(t *testing.T) {
tt := []struct {
name string
expirationEnabled bool
@ -95,7 +95,6 @@ func TestAccountManager_GetNetworkMap(t *testing.T) {
Key: peerKey1.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-1"},
})
if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err)
return
@ -124,6 +123,7 @@ func TestAccountManager_GetNetworkMap(t *testing.T) {
if len(networkMap.Peers) != 1 {
t.Errorf("expecting Account NetworkMap to have 1 peers, got %v", len(networkMap.Peers))
return
}
if networkMap.Peers[0].Key != peerKey2.PublicKey().String() {
@ -135,7 +135,7 @@ func TestAccountManager_GetNetworkMap(t *testing.T) {
}
}
func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) {
manager, err := createManager(t)
if err != nil {
t.Fatal(err)
@ -166,7 +166,6 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
Key: peerKey1.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-1"},
})
if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err)
return
@ -181,19 +180,18 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
Key: peerKey2.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-2"},
})
if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err)
return
}
rules, err := manager.ListRules(account.Id, userID)
policies, err := manager.ListPolicies(account.Id, userID)
if err != nil {
t.Errorf("expecting to get a list of rules, got failure %v", err)
return
}
err = manager.DeleteRule(account.Id, rules[0].ID, userID)
err = manager.DeletePolicy(account.Id, policies[0].ID, userID)
if err != nil {
t.Errorf("expecting to delete 1 group, got failure %v", err)
return
@ -201,14 +199,14 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
var (
group1 Group
group2 Group
rule Rule
policy Policy
)
group1.ID = xid.New().String()
group2.ID = xid.New().String()
group1.Name = "src"
group2.Name = "dst"
rule.ID = xid.New().String()
policy.ID = xid.New().String()
group1.Peers = append(group1.Peers, peer1.ID)
group2.Peers = append(group2.Peers, peer2.ID)
@ -223,11 +221,21 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
return
}
rule.Name = "test"
rule.Source = append(rule.Source, group1.ID)
rule.Destination = append(rule.Destination, group2.ID)
rule.Flow = TrafficFlowBidirect
err = manager.SaveRule(account.Id, userID, &rule)
policy.Name = "test"
policy.Enabled = true
policy.Rules = []*PolicyRule{
{
Enabled: true,
Sources: []string{group1.ID},
Destinations: []string{group2.ID},
Action: PolicyTrafficActionAccept,
},
}
if err := policy.UpdateQueryFromRules(); err != nil {
t.Errorf("expecting policy to be updated, got failure %v", err)
return
}
err = manager.SavePolicy(account.Id, userID, &policy)
if err != nil {
t.Errorf("expecting rule to be added, got failure %v", err)
return
@ -274,8 +282,8 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
)
}
rule.Disabled = true
err = manager.SaveRule(account.Id, userID, &rule)
policy.Enabled = false
err = manager.SavePolicy(account.Id, userID, &policy)
if err != nil {
t.Errorf("expecting rule to be added, got failure %v", err)
return
@ -333,7 +341,6 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) {
Key: peerKey1.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-1"},
})
if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err)
return
@ -363,7 +370,6 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) {
if account.Network.Id != network.Id {
t.Errorf("expecting Account Networks ID to be equal, got %s expected %s", network.Id, account.Network.Id)
}
}
func TestDefaultAccountManager_GetPeer(t *testing.T) {
@ -400,7 +406,6 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
Key: peerKey1.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-2"},
})
if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err)
return
@ -417,7 +422,6 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
Key: peerKey2.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-2"},
})
if err != nil {
t.Fatal(err)
return
@ -439,9 +443,9 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
}
assert.NotNil(t, peer)
// delete the all-to-all rule so that user's peer1 has no access to peer2
for _, rule := range account.Rules {
err = manager.DeleteRule(accountID, rule.ID, adminUser)
// delete the all-to-all policy so that user's peer1 has no access to peer2
for _, policy := range account.Policies {
err = manager.DeletePolicy(accountID, policy.ID, adminUser)
if err != nil {
t.Fatal(err)
return
@ -466,11 +470,9 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
return
}
assert.NotNil(t, peer)
}
func getSetupKey(account *Account, keyType SetupKeyType) *SetupKey {
var setupKey *SetupKey
for _, key := range account.SetupKeys {
if key.Type == keyType {

451
management/server/policy.go Normal file
View File

@ -0,0 +1,451 @@
package server
import (
"bytes"
"context"
_ "embed"
"fmt"
"html/template"
"strings"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/status"
"github.com/open-policy-agent/opa/rego"
log "github.com/sirupsen/logrus"
)
// PolicyUpdateOperationType operation type
type PolicyUpdateOperationType int
// PolicyTrafficActionType action type for the firewall
type PolicyTrafficActionType string
const (
// PolicyTrafficActionAccept indicates that the traffic is accepted
PolicyTrafficActionAccept = PolicyTrafficActionType("accept")
// PolicyTrafficActionDrop indicates that the traffic is dropped
PolicyTrafficActionDrop = PolicyTrafficActionType("drop")
)
// PolicyUpdateOperation operation object with type and values to be applied
type PolicyUpdateOperation struct {
Type PolicyUpdateOperationType
Values []string
}
//go:embed rego/default_policy_module.rego
var defaultPolicyModule string
//go:embed rego/default_policy.rego
var defaultPolicyText string
// defaultPolicyTemplate is a template for the default policy
var defaultPolicyTemplate = template.Must(template.New("policy").Parse(defaultPolicyText))
// PolicyRule is the metadata of the policy
type PolicyRule struct {
// ID of the policy rule
ID string
// Name of the rule visible in the UI
Name string
// Description of the rule visible in the UI
Description string
// Enabled status of rule in the system
Enabled bool
// Action policy accept or drops packets
Action PolicyTrafficActionType
// Destinations policy destination groups
Destinations []string
// Sources policy source groups
Sources []string
}
// Copy returns a copy of a policy rule
func (pm *PolicyRule) Copy() *PolicyRule {
return &PolicyRule{
ID: pm.ID,
Name: pm.Name,
Description: pm.Description,
Enabled: pm.Enabled,
Action: pm.Action,
Destinations: pm.Destinations[:],
Sources: pm.Sources[:],
}
}
// ToRule converts the PolicyRule to a legacy representation of the Rule (for backwards compatibility)
func (pm *PolicyRule) ToRule() *Rule {
return &Rule{
ID: pm.ID,
Name: pm.Name,
Description: pm.Description,
Disabled: !pm.Enabled,
Flow: TrafficFlowBidirect,
Destination: pm.Destinations,
Source: pm.Sources,
}
}
// Policy of the Rego query
type Policy struct {
// ID of the policy
ID string
// Name of the Policy
Name string
// Description of the policy visible in the UI
Description string
// Enabled status of the policy
Enabled bool
// Query of Rego the policy
Query string
// Rules of the policy
Rules []*PolicyRule
}
// Copy returns a copy of the policy.
func (p *Policy) Copy() *Policy {
c := &Policy{
ID: p.ID,
Name: p.Name,
Description: p.Description,
Enabled: p.Enabled,
Query: p.Query,
}
for _, r := range p.Rules {
c.Rules = append(c.Rules, r.Copy())
}
return c
}
// EventMeta returns activity event meta related to this policy
func (p *Policy) EventMeta() map[string]any {
return map[string]any{"name": p.Name}
}
// UpdateQueryFromRules marshals policy rules to Rego string and set it to Query
func (p *Policy) UpdateQueryFromRules() error {
type templateVars struct {
All []string
Source []string
Destination []string
}
queries := []string{}
for _, r := range p.Rules {
if !r.Enabled {
continue
}
buff := new(bytes.Buffer)
input := templateVars{
All: append(r.Destinations[:], r.Sources...),
Source: r.Sources,
Destination: r.Destinations,
}
if err := defaultPolicyTemplate.Execute(buff, input); err != nil {
return status.Errorf(status.BadRequest, "failed to update policy query: %v", err)
}
queries = append(queries, buff.String())
}
p.Query = strings.Join(queries, "\n")
return nil
}
// FirewallRule is a rule of the firewall.
type FirewallRule struct {
// PeerID of the peer
PeerID string
// PeerIP of the peer
PeerIP string
// Direction of the traffic
Direction string
// Action of the traffic
Action string
// Port of the traffic
Port string
}
// parseFromRegoResult parses the Rego result to a FirewallRule.
func (f *FirewallRule) parseFromRegoResult(value interface{}) error {
object, ok := value.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid Rego query eval result")
}
peerID, ok := object["ID"].(string)
if !ok {
return fmt.Errorf("invalid Rego query eval result peer ID type")
}
peerIP, ok := object["IP"].(string)
if !ok {
return fmt.Errorf("invalid Rego query eval result peer IP type")
}
direction, ok := object["Direction"].(string)
if !ok {
return fmt.Errorf("invalid Rego query eval result peer direction type")
}
action, ok := object["Action"].(string)
if !ok {
return fmt.Errorf("invalid Rego query eval result peer action type")
}
port, ok := object["Port"].(string)
if !ok {
return fmt.Errorf("invalid Rego query eval result peer port type")
}
f.PeerID = peerID
f.PeerIP = peerIP
f.Direction = direction
f.Action = action
f.Port = port
return nil
}
// getRegoQuery returns a initialized Rego object with default rule.
func (a *Account) getRegoQuery() (rego.PreparedEvalQuery, error) {
queries := []func(*rego.Rego){
rego.Query("data.netbird.all"),
rego.Module("netbird", defaultPolicyModule),
}
for i, p := range a.Policies {
if !p.Enabled {
continue
}
queries = append(queries, rego.Module(fmt.Sprintf("netbird-%d", i), p.Query))
}
return rego.New(queries...).PrepareForEval(context.TODO())
}
// getPeersByPolicy returns all peers that given peer has access to.
func (a *Account) getPeersByPolicy(peerID string) ([]*Peer, []*FirewallRule) {
input := map[string]interface{}{
"peer_id": peerID,
"peers": a.Peers,
"groups": a.Groups,
}
query, err := a.getRegoQuery()
if err != nil {
log.WithError(err).Error("get Rego query")
return nil, nil
}
evalResult, err := query.Eval(
context.TODO(),
rego.EvalInput(input),
)
if err != nil {
log.WithError(err).Error("eval Rego query")
return nil, nil
}
if len(evalResult) == 0 || len(evalResult[0].Expressions) == 0 {
log.Trace("empty Rego query eval result")
return nil, nil
}
expressions, ok := evalResult[0].Expressions[0].Value.([]interface{})
if !ok {
return nil, nil
}
dst := make(map[string]struct{})
src := make(map[string]struct{})
peers := make([]*Peer, 0, len(expressions))
rules := make([]*FirewallRule, 0, len(expressions))
for _, v := range expressions {
rule := &FirewallRule{}
if err := rule.parseFromRegoResult(v); err != nil {
log.WithError(err).Error("parse Rego query eval result")
continue
}
rules = append(rules, rule)
switch rule.Direction {
case "dst":
if _, ok := dst[rule.PeerID]; ok {
continue
}
dst[rule.PeerID] = struct{}{}
case "src":
if _, ok := src[rule.PeerID]; ok {
continue
}
src[rule.PeerID] = struct{}{}
default:
log.WithField("direction", rule.Direction).Error("invalid direction")
continue
}
}
added := make(map[string]struct{})
if _, ok := src[peerID]; ok {
for id := range dst {
if _, ok := added[id]; !ok && id != peerID {
added[id] = struct{}{}
}
}
}
if _, ok := dst[peerID]; ok {
for id := range src {
if _, ok := added[id]; !ok && id != peerID {
added[id] = struct{}{}
}
}
}
for id := range added {
peers = append(peers, a.Peers[id])
}
return peers, rules
}
// GetPolicy from the store
func (am *DefaultAccountManager) GetPolicy(accountID, policyID, userID string) (*Policy, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return nil, err
}
user, err := account.FindUser(userID)
if err != nil {
return nil, err
}
if !user.IsAdmin() {
return nil, status.Errorf(status.PermissionDenied, "only admins are allowed to view policies")
}
for _, policy := range account.Policies {
if policy.ID == policyID {
return policy, nil
}
}
return nil, status.Errorf(status.NotFound, "policy with ID %s not found", policyID)
}
// SavePolicy in the store
func (am *DefaultAccountManager) SavePolicy(accountID, userID string, policy *Policy) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return err
}
exists := am.savePolicy(account, policy)
account.Network.IncSerial()
if err = am.Store.SaveAccount(account); err != nil {
return err
}
action := activity.PolicyAdded
if exists {
action = activity.PolicyUpdated
}
am.storeEvent(userID, policy.ID, accountID, action, policy.EventMeta())
return am.updateAccountPeers(account)
}
// DeletePolicy from the store
func (am *DefaultAccountManager) DeletePolicy(accountID, policyID, userID string) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return err
}
policy, err := am.deletePolicy(account, policyID)
if err != nil {
return err
}
account.Network.IncSerial()
if err = am.Store.SaveAccount(account); err != nil {
return err
}
am.storeEvent(userID, policy.ID, accountID, activity.PolicyRemoved, policy.EventMeta())
return am.updateAccountPeers(account)
}
// ListPolicies from the store
func (am *DefaultAccountManager) ListPolicies(accountID, userID string) ([]*Policy, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return nil, err
}
user, err := account.FindUser(userID)
if err != nil {
return nil, err
}
if !user.IsAdmin() {
return nil, status.Errorf(status.PermissionDenied, "Only Administrators can view policies")
}
return account.Policies[:], nil
}
func (am *DefaultAccountManager) deletePolicy(account *Account, policyID string) (*Policy, error) {
policyIdx := -1
for i, policy := range account.Policies {
if policy.ID == policyID {
policyIdx = i
break
}
}
if policyIdx < 0 {
return nil, status.Errorf(status.NotFound, "rule with ID %s doesn't exist", policyID)
}
policy := account.Policies[policyIdx]
account.Policies = append(account.Policies[:policyIdx], account.Policies[policyIdx+1:]...)
return policy, nil
}
func (am *DefaultAccountManager) savePolicy(account *Account, policy *Policy) (exists bool) {
for i, p := range account.Policies {
if p.ID == policy.ID {
account.Policies[i] = policy
exists = true
break
}
}
if !exists {
account.Policies = append(account.Policies, policy)
}
return
}

View File

@ -0,0 +1,67 @@
package server
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAccount_getPeersByPolicy(t *testing.T) {
account := &Account{
Peers: map[string]*Peer{
"peer1": {
ID: "peer1",
IP: net.IPv4(10, 20, 0, 1),
},
"peer2": {
ID: "peer2",
IP: net.IPv4(10, 20, 0, 2),
},
"peer3": {
ID: "peer3",
IP: net.IPv4(10, 20, 0, 3),
},
},
Groups: map[string]*Group{
"gid1": {
ID: "gid1",
Name: "all",
Peers: []string{"peer1", "peer2", "peer3"},
},
},
Rules: map[string]*Rule{
"default": {
ID: "default",
Name: "default",
Description: "default",
Disabled: false,
Source: []string{"gid1"},
Destination: []string{"gid1"},
},
},
}
rule, err := RuleToPolicy(account.Rules["default"])
assert.NoError(t, err)
account.Policies = append(account.Policies, rule)
peers, firewallRules := account.getPeersByPolicy("peer1")
assert.Len(t, peers, 2)
assert.Contains(t, peers, account.Peers["peer2"])
assert.Contains(t, peers, account.Peers["peer3"])
epectedFirewallRules := []*FirewallRule{
{PeerID: "peer1", PeerIP: "10.20.0.1", Direction: "dst", Action: "accept", Port: ""},
{PeerID: "peer2", PeerIP: "10.20.0.2", Direction: "dst", Action: "accept", Port: ""},
{PeerID: "peer3", PeerIP: "10.20.0.3", Direction: "dst", Action: "accept", Port: ""},
{PeerID: "peer1", PeerIP: "10.20.0.1", Direction: "src", Action: "accept", Port: ""},
{PeerID: "peer2", PeerIP: "10.20.0.2", Direction: "src", Action: "accept", Port: ""},
{PeerID: "peer3", PeerIP: "10.20.0.3", Direction: "src", Action: "accept", Port: ""},
}
assert.Len(t, firewallRules, len(epectedFirewallRules))
for i := range firewallRules {
assert.Equal(t, firewallRules[i], epectedFirewallRules[i])
}
}

View File

@ -0,0 +1,9 @@
package netbird
all[rule] {
is_peer_in_any_group([{{range $i, $e := .All}}{{if $i}},{{end}}"{{$e}}"{{end}}])
rule := array.concat(
rules_from_groups([{{range $i, $e := .Destination}}{{if $i}},{{end}}"{{$e}}"{{end}}], "dst", "accept", ""),
rules_from_groups([{{range $i, $e := .Source}}{{if $i}},{{end}}"{{$e}}"{{end}}], "src", "accept", ""),
)[_]
}

View File

@ -0,0 +1,40 @@
package netbird
import future.keywords.if
import future.keywords.in
import future.keywords.contains
# get_rule builds a netbird rule object from given parameters
get_rule(peer_id, direction, action, port) := rule if {
peer := input.peers[_]
peer.ID == peer_id
rule := {
"ID": peer.ID,
"IP": peer.IP,
"Direction": direction,
"Action": action,
"Port": port,
}
}
# peers_from_group returns a list of peer ids for a given group id
peers_from_group(group_id) := peers if {
group := input.groups[_]
group.ID == group_id
peers := [peer | peer := group.Peers[_]]
}
# netbird_rules_from_groups returns a list of netbird rules for a given list of group names
rules_from_groups(groups, direction, action, port) := rules if {
group_id := groups[_]
rules := [get_rule(peer, direction, action, port) | peer := peers_from_group(group_id)[_]]
}
# is_peer_in_any_group checks that input peer present at least in one group
is_peer_in_any_group(groups) := count([group_id]) > 0 if {
group_id := groups[_]
group := input.groups[_]
group.ID == group_id
peer := group.Peers[_]
peer == input.peer_id
}

View File

@ -1,12 +1,13 @@
package server
import (
"net/netip"
"testing"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/route"
"github.com/rs/xid"
"github.com/stretchr/testify/require"
"net/netip"
"testing"
)
const (
@ -21,7 +22,6 @@ const (
)
func TestCreateRoute(t *testing.T) {
type input struct {
network string
netID string
@ -265,13 +265,11 @@ func TestCreateRoute(t *testing.T) {
if !testCase.expectedRoute.IsEqual(outRoute) {
t.Errorf("new route didn't match expected route:\nGot %#v\nExpected:%#v\n", outRoute, testCase.expectedRoute)
}
})
}
}
func TestSaveRoute(t *testing.T) {
validPeer := peer2ID
invalidPeer := "nonExisting"
validPrefix := netip.MustParsePrefix("192.168.0.0/24")
@ -521,7 +519,6 @@ func TestSaveRoute(t *testing.T) {
if !testCase.expectedRoute.IsEqual(savedRoute) {
t.Errorf("new route didn't match expected route:\nGot %#v\nExpected:%#v\n", savedRoute, testCase.expectedRoute)
}
})
}
}
@ -781,13 +778,11 @@ func TestUpdateRoute(t *testing.T) {
if !testCase.expectedRoute.IsEqual(updatedRoute) {
t.Errorf("new route didn't match expected route:\nGot %#v\nExpected:%#v\n", updatedRoute, testCase.expectedRoute)
}
})
}
}
func TestDeleteRoute(t *testing.T) {
testingRoute := &route.Route{
ID: "testingRoute",
Network: netip.MustParsePrefix("192.168.0.0/16"),
@ -906,20 +901,22 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
err = am.SaveGroup(account.Id, userID, newGroup)
require.NoError(t, err)
rules, err := am.ListRules(account.Id, "testingUser")
rules, err := am.ListPolicies(account.Id, "testingUser")
require.NoError(t, err)
defaultRule := rules[0]
newRule := defaultRule.Copy()
newRule.ID = xid.New().String()
newRule.Name = "peer1 only"
newRule.Source = []string{newGroup.ID}
newRule.Destination = []string{newGroup.ID}
err = am.SaveRule(account.Id, userID, newRule)
newPolicy := defaultRule.Copy()
newPolicy.ID = xid.New().String()
newPolicy.Name = "peer1 only"
newPolicy.Rules[0].Sources = []string{newGroup.ID}
newPolicy.Rules[0].Destinations = []string{newGroup.ID}
err = newPolicy.UpdateQueryFromRules()
require.NoError(t, err)
err = am.DeleteRule(account.Id, defaultRule.ID, userID)
err = am.SavePolicy(account.Id, userID, newPolicy)
require.NoError(t, err)
err = am.DeletePolicy(account.Id, defaultRule.ID, userID)
require.NoError(t, err)
peer1GroupRoutes, err := am.GetNetworkMap(peer1ID)
@ -936,7 +933,6 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
peer1DeletedRoute, err := am.GetNetworkMap(peer1ID)
require.NoError(t, err)
require.Len(t, peer1DeletedRoute.Routes, 0, "we should receive one route for peer1")
}
func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
@ -959,7 +955,6 @@ func createRouterStore(t *testing.T) (Store, error) {
}
func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) {
accountID := "testingAcc"
domain := "example.com"

View File

@ -1,10 +1,6 @@
package server
import (
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/status"
"strings"
)
import "fmt"
// TrafficFlowType defines allowed direction of the traffic in the rule
type TrafficFlowType int
@ -18,6 +14,10 @@ const (
DefaultRuleName = "Default"
// DefaultRuleDescription is a description for the Default rule that is created for every account
DefaultRuleDescription = "This is a default rule that allows connections between all the resources"
// DefaultPolicyName is a name for the Default policy that is created for every account
DefaultPolicyName = "Default"
// DefaultPolicyDescription is a description for the Default policy that is created for every account
DefaultPolicyDescription = "This is a default policy that allows connections between all the resources"
)
// Rule of ACL for groups
@ -44,38 +44,6 @@ type Rule struct {
Flow TrafficFlowType
}
const (
// UpdateRuleName indicates a rule name update operation
UpdateRuleName RuleUpdateOperationType = iota
// UpdateRuleDescription indicates a rule description update operation
UpdateRuleDescription
// UpdateRuleStatus indicates a rule status update operation
UpdateRuleStatus
// UpdateRuleFlow indicates a rule flow update operation
UpdateRuleFlow
// InsertGroupsToSource indicates an insert groups to source rule operation
InsertGroupsToSource
// RemoveGroupsFromSource indicates an remove groups from source rule operation
RemoveGroupsFromSource
// UpdateSourceGroups indicates a replacement of source group list of a rule operation
UpdateSourceGroups
// InsertGroupsToDestination indicates an insert groups to destination rule operation
InsertGroupsToDestination
// RemoveGroupsFromDestination indicates an remove groups from destination rule operation
RemoveGroupsFromDestination
// UpdateDestinationGroups indicates a replacement of destination group list of a rule operation
UpdateDestinationGroups
)
// RuleUpdateOperationType operation type
type RuleUpdateOperationType int
// RuleUpdateOperation operation object with type and values to be applied
type RuleUpdateOperation struct {
Type RuleUpdateOperationType
Values []string
}
func (r *Rule) Copy() *Rule {
return &Rule{
ID: r.ID,
@ -93,185 +61,36 @@ func (r *Rule) EventMeta() map[string]any {
return map[string]any{"name": r.Name}
}
// GetRule of ACL from the store
func (am *DefaultAccountManager) GetRule(accountID, ruleID, userID string) (*Rule, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return nil, err
// ToPolicyRule converts a Rule to a PolicyRule object
func (r *Rule) ToPolicyRule() *PolicyRule {
if r == nil {
return nil
}
user, err := account.FindUser(userID)
if err != nil {
return nil, err
return &PolicyRule{
ID: r.ID,
Name: r.Name,
Enabled: !r.Disabled,
Description: r.Description,
Action: PolicyTrafficActionAccept,
Destinations: r.Destination,
Sources: r.Source,
}
if !user.IsAdmin() {
return nil, status.Errorf(status.PermissionDenied, "only admins are allowed to view rules")
}
rule, ok := account.Rules[ruleID]
if ok {
return rule, nil
}
return nil, status.Errorf(status.NotFound, "rule with ID %s not found", ruleID)
}
// SaveRule of ACL in the store
func (am *DefaultAccountManager) SaveRule(accountID, userID string, rule *Rule) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return err
}
_, exists := account.Rules[rule.ID]
account.Rules[rule.ID] = rule
account.Network.IncSerial()
if err = am.Store.SaveAccount(account); err != nil {
return err
}
action := activity.RuleAdded
if exists {
action = activity.RuleUpdated
}
am.storeEvent(userID, rule.ID, accountID, action, rule.EventMeta())
return am.updateAccountPeers(account)
}
// UpdateRule updates a rule using a list of operations
func (am *DefaultAccountManager) UpdateRule(accountID string, ruleID string,
operations []RuleUpdateOperation) (*Rule, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return nil, err
}
ruleToUpdate, ok := account.Rules[ruleID]
if !ok {
return nil, status.Errorf(status.NotFound, "rule %s no longer exists", ruleID)
}
rule := ruleToUpdate.Copy()
for _, operation := range operations {
switch operation.Type {
case UpdateRuleName:
rule.Name = operation.Values[0]
case UpdateRuleDescription:
rule.Description = operation.Values[0]
case UpdateRuleFlow:
if operation.Values[0] != TrafficFlowBidirectString {
return nil, status.Errorf(status.InvalidArgument, "failed to parse flow")
}
rule.Flow = TrafficFlowBidirect
case UpdateRuleStatus:
if strings.ToLower(operation.Values[0]) == "true" {
rule.Disabled = true
} else if strings.ToLower(operation.Values[0]) == "false" {
rule.Disabled = false
} else {
return nil, status.Errorf(status.InvalidArgument, "failed to parse status")
}
case UpdateSourceGroups:
rule.Source = operation.Values
case InsertGroupsToSource:
sourceList := rule.Source
resultList := removeFromList(sourceList, operation.Values)
rule.Source = append(resultList, operation.Values...)
case RemoveGroupsFromSource:
sourceList := rule.Source
resultList := removeFromList(sourceList, operation.Values)
rule.Source = resultList
case UpdateDestinationGroups:
rule.Destination = operation.Values
case InsertGroupsToDestination:
sourceList := rule.Destination
resultList := removeFromList(sourceList, operation.Values)
rule.Destination = append(resultList, operation.Values...)
case RemoveGroupsFromDestination:
sourceList := rule.Destination
resultList := removeFromList(sourceList, operation.Values)
rule.Destination = resultList
}
}
account.Rules[ruleID] = rule
account.Network.IncSerial()
if err = am.Store.SaveAccount(account); err != nil {
return nil, err
}
err = am.updateAccountPeers(account)
if err != nil {
return nil, status.Errorf(status.Internal, "failed to update account peers")
}
return rule, nil
}
// DeleteRule of ACL from the store
func (am *DefaultAccountManager) DeleteRule(accountID, ruleID, userID string) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return err
}
rule := account.Rules[ruleID]
// RuleToPolicy converts a Rule to a Policy query object
func RuleToPolicy(rule *Rule) (*Policy, error) {
if rule == nil {
return status.Errorf(status.NotFound, "rule with ID %s doesn't exist", ruleID)
return nil, fmt.Errorf("rule is empty")
}
delete(account.Rules, ruleID)
account.Network.IncSerial()
if err = am.Store.SaveAccount(account); err != nil {
return err
policy := &Policy{
ID: rule.ID,
Name: rule.Name,
Description: rule.Description,
Enabled: !rule.Disabled,
Rules: []*PolicyRule{rule.ToPolicyRule()},
}
am.storeEvent(userID, rule.ID, accountID, activity.RuleRemoved, rule.EventMeta())
return am.updateAccountPeers(account)
}
// ListRules of ACL from the store
func (am *DefaultAccountManager) ListRules(accountID, userID string) ([]*Rule, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
if err := policy.UpdateQueryFromRules(); err != nil {
return nil, err
}
user, err := account.FindUser(userID)
if err != nil {
return nil, err
}
if !user.IsAdmin() {
return nil, status.Errorf(status.PermissionDenied, "Only Administrators can view Access Rules")
}
rules := make([]*Rule, 0, len(account.Rules))
for _, item := range account.Rules {
rules = append(rules, item)
}
return rules, nil
return policy, nil
}

View File

@ -0,0 +1,116 @@
{
"Accounts": {
"bf1c8084-ba50-4ce7-9439-34653001fc3b": {
"Id": "bf1c8084-ba50-4ce7-9439-34653001fc3b",
"Domain": "test.com",
"DomainCategory": "private",
"IsDomainPrimaryAccount": true,
"SetupKeys": {
"A2C8E62B-38F5-4553-B31E-DD66C696CEBB": {
"Key": "A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
"Name": "Default key",
"Type": "reusable",
"CreatedAt": "2021-08-19T20:46:20.005936822+02:00",
"ExpiresAt": "2321-09-18T20:46:20.005936822+02:00",
"Revoked": false,
"UsedTimes": 0
}
},
"Network": {
"Id": "af1c8024-ha40-4ce2-9418-34653101fc3c",
"Net": {
"IP": "100.64.0.0",
"Mask": "//8AAA=="
},
"Dns": null
},
"Peers": {
"cfefqs706sqkneg59g4g": {
"ID": "cfefqs706sqkneg59g4g",
"Key": "MI5mHfJhbggPfD3FqEIsXm8X5bSWeUI2LhO9MpEEtWA=",
"SetupKey": "",
"IP": "100.103.179.238",
"Meta": {
"Hostname": "Ubuntu-2204-jammy-amd64-base",
"GoOS": "linux",
"Kernel": "Linux",
"Core": "22.04",
"Platform": "x86_64",
"OS": "Ubuntu",
"WtVersion": "development",
"UIVersion": ""
},
"Name": "crocodile",
"DNSLabel": "crocodile",
"Status": {
"LastSeen": "2023-02-13T12:37:12.635454796Z",
"Connected": true
},
"UserID": "edafee4e-63fb-11ec-90d6-0242ac120003",
"SSHKey": "AAAAC3NzaC1lZDI1NTE5AAAAIJN1NM4bpB9K",
"SSHEnabled": false
},
"cfeg6sf06sqkneg59g50": {
"ID": "cfeg6sf06sqkneg59g50",
"Key": "zMAOKUeIYIuun4n0xPR1b3IdYZPmsyjYmB2jWCuloC4=",
"SetupKey": "",
"IP": "100.103.26.180",
"Meta": {
"Hostname": "borg",
"GoOS": "linux",
"Kernel": "Linux",
"Core": "22.04",
"Platform": "x86_64",
"OS": "Ubuntu",
"WtVersion": "development",
"UIVersion": ""
},
"Name": "dingo",
"DNSLabel": "dingo",
"Status": {
"LastSeen": "2023-02-21T09:37:42.565899199Z",
"Connected": false
},
"UserID": "f4f6d672-63fb-11ec-90d6-0242ac120003",
"SSHKey": "AAAAC3NzaC1lZDI1NTE5AAAAILHW",
"SSHEnabled": true
}
},
"Groups": {
"cfefqs706sqkneg59g3g": {
"ID": "cfefqs706sqkneg59g3g",
"Name": "All",
"Peers": [
"cfefqs706sqkneg59g4g",
"cfeg6sf06sqkneg59g50"
]
}
},
"Rules": {
"cfefqs706sqkneg59g40": {
"ID": "cfefqs706sqkneg59g40",
"Name": "Default",
"Description": "This is a default rule that allows connections between all the resources",
"Disabled": false,
"Source": [
"cfefqs706sqkneg59g3g"
],
"Destination": [
"cfefqs706sqkneg59g3g"
],
"Flow": 0
}
},
"Users": {
"edafee4e-63fb-11ec-90d6-0242ac120003": {
"Id": "edafee4e-63fb-11ec-90d6-0242ac120003",
"Role": "admin"
},
"f4f6d672-63fb-11ec-90d6-0242ac120003": {
"Id": "f4f6d672-63fb-11ec-90d6-0242ac120003",
"Role": "user"
}
}
}
}
}