diff --git a/go.mod b/go.mod index dd030b2..fab3d2b 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 7818e1c..dd6b6a3 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/management/server/account.go b/management/server/account.go index e9b0df3..5b9c940 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -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 } diff --git a/management/server/account_test.go b/management/server/account_test.go index 4d19da2..fad5bf5 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -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) { diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index 1e74414..a4a4643 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -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: diff --git a/management/server/file_store.go b/management/server/file_store.go index f2aa5f5..838875c 100644 --- a/management/server/file_store.go +++ b/management/server/file_store.go @@ -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 { diff --git a/management/server/file_store_test.go b/management/server/file_store_test.go index dadcb3e..18de218 100644 --- a/management/server/file_store_test.go +++ b/management/server/file_store_test.go @@ -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() diff --git a/management/server/group.go b/management/server/group.go index 2959f3a..688d6da 100644 --- a/management/server/group.go +++ b/management/server/group.go @@ -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() diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index 0b33aba..b3d954a 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -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,8 +1410,8 @@ paths: "$ref": "#/components/responses/forbidden" '500': "$ref": "#/components/responses/internal_error" - patch: - summary: Update information about a Rule + delete: + summary: Delete a Rule tags: [ Rules ] security: - BearerAuth: [ ] @@ -1349,21 +1422,114 @@ paths: schema: type: string description: The Rule 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/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: Update Rule request using a list of json patch objects + description: New Policy request content: 'application/json': schema: - type: array - items: - $ref: '#/components/schemas/RulePatchOperation' + allOf: + - $ref: '#/components/schemas/PolicyMinimum' responses: '200': - description: A Rule object + description: A Policy Object content: application/json: schema: - $ref: '#/components/schemas/Rule' + $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': @@ -1373,8 +1539,8 @@ paths: '500': "$ref": "#/components/responses/internal_error" delete: - summary: Delete a Rule - tags: [ Rules ] + summary: Delete a Policy + tags: [ Policies ] security: - BearerAuth: [ ] parameters: @@ -1383,7 +1549,7 @@ paths: required: true schema: type: string - description: The Rule ID + description: The Policy ID responses: '200': description: Delete status code @@ -1396,7 +1562,6 @@ paths: "$ref": "#/components/responses/forbidden" '500': "$ref": "#/components/responses/internal_error" - /api/routes: get: summary: Returns a list of all routes diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index 9d828cd..372ecd1 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -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 / - 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 / -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 diff --git a/management/server/http/handler.go b/management/server/http/handler.go index 163e818..90f62e7 100644 --- a/management/server/http/handler.go +++ b/management/server/http/handler.go @@ -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") diff --git a/management/server/http/policies.go b/management/server/http/policies.go new file mode 100644 index 0000000..275992d --- /dev/null +++ b/management/server/http/policies.go @@ -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 +} diff --git a/management/server/http/rules_handler.go b/management/server/http/rules_handler.go index 2dd2e75..8925c37 100644 --- a/management/server/http/rules_handler.go +++ b/management/server/http/rules_handler.go @@ -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, - } - - switch req.Flow { - case server.TrafficFlowBidirectString: - rule.Flow = server.TrafficFlowBidirect - default: - util.WriteError(status.Errorf(status.InvalidArgument, "unknown flow type"), w) - return - } - - err = h.accountManager.SaveRule(account.Id, user.Id, &rule) - if err != nil { - util.WriteError(err, w) + if len(policy.Rules) != 1 { + util.WriteError(status.Errorf(status.Internal, "policy should contain exactly one rule"), 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 { + 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 } - 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) + switch req.Flow { + case server.TrafficFlowBidirectString: + policy.Rules[0].Action = server.PolicyTrafficActionAccept + default: + util.WriteError(status.Errorf(status.InvalidArgument, "unknown flow type"), 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) + err = h.accountManager.SavePolicy(account.Id, user.Id, policy) 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) } diff --git a/management/server/http/rules_handler_test.go b/management/server/http/rules_handler_test.go index ed74c97..a1d2cbd 100644 --- a/management/server/http/rules_handler_test.go +++ b/management/server/http/rules_handler_test.go @@ -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) }) diff --git a/management/server/management_test.go b/management/server/management_test.go index 451a9aa..d892fec 100644 --- a/management/server/management_test.go +++ b/management/server/management_test.go @@ -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() { diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 713ccee..9da7487 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -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) } diff --git a/management/server/peer.go b/management/server/peer.go index 03c1d4e..b5505f9 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -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 { diff --git a/management/server/peer_test.go b/management/server/peer_test.go index c9168ef..6adfc55 100644 --- a/management/server/peer_test.go +++ b/management/server/peer_test.go @@ -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 { diff --git a/management/server/policy.go b/management/server/policy.go new file mode 100644 index 0000000..31f6bb6 --- /dev/null +++ b/management/server/policy.go @@ -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 +} diff --git a/management/server/policy_test.go b/management/server/policy_test.go new file mode 100644 index 0000000..73663a8 --- /dev/null +++ b/management/server/policy_test.go @@ -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]) + } +} diff --git a/management/server/rego/default_policy.rego b/management/server/rego/default_policy.rego new file mode 100644 index 0000000..92e975a --- /dev/null +++ b/management/server/rego/default_policy.rego @@ -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", ""), + )[_] +} diff --git a/management/server/rego/default_policy_module.rego b/management/server/rego/default_policy_module.rego new file mode 100644 index 0000000..846e22e --- /dev/null +++ b/management/server/rego/default_policy_module.rego @@ -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 +} diff --git a/management/server/route_test.go b/management/server/route_test.go index b59e2f8..5145fad 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -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} + 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.SaveRule(account.Id, userID, newRule) + err = am.SavePolicy(account.Id, userID, newPolicy) require.NoError(t, err) - err = am.DeleteRule(account.Id, defaultRule.ID, userID) + 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" diff --git a/management/server/rule.go b/management/server/rule.go index a69ae47..b7e24d0 100644 --- a/management/server/rule.go +++ b/management/server/rule.go @@ -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 - } - - user, err := account.FindUser(userID) - if err != nil { - return nil, err +// ToPolicyRule converts a Rule to a PolicyRule object +func (r *Rule) ToPolicyRule() *PolicyRule { + if r == nil { + return nil } - - 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 &PolicyRule{ + ID: r.ID, + Name: r.Name, + Enabled: !r.Disabled, + Description: r.Description, + Action: PolicyTrafficActionAccept, + Destinations: r.Destination, + Sources: r.Source, } - - 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 } diff --git a/management/server/testdata/store_policy_migrate.json b/management/server/testdata/store_policy_migrate.json new file mode 100644 index 0000000..1b046e6 --- /dev/null +++ b/management/server/testdata/store_policy_migrate.json @@ -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" + } + } + } + } +}