From ab78a625ff353930b62247d0872a7819832ad479 Mon Sep 17 00:00:00 2001 From: Bethuel Mmbaga Date: Tue, 4 Jun 2024 11:59:53 +0300 Subject: [PATCH] Extend network route with access control groups (#2027) * extends route with access control groups * add support for creating and updating routes with access control groups * Add access control groups to routes API request and response * fix tests * fix tests --- management/server/account.go | 2 +- management/server/account_test.go | 7 +- management/server/http/api/openapi.yml | 6 + management/server/http/api/types.gen.go | 6 + management/server/http/routes_handler.go | 16 +- management/server/http/routes_handler_test.go | 64 +++++-- management/server/mock_server/account_mock.go | 6 +- management/server/route.go | 17 +- management/server/route_test.go | 177 ++++++++++-------- route/route.go | 50 ++--- 10 files changed, 224 insertions(+), 127 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index 86e429bb2e1..7e7c2ac45e2 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -101,7 +101,7 @@ type AccountManager interface { DeletePolicy(accountID, policyID, userID string) error ListPolicies(accountID, userID string) ([]*Policy, error) GetRoute(accountID string, routeID route.ID, userID string) (*route.Route, error) - CreateRoute(accountID, prefix, peerID string, peerGroupIDs []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) + CreateRoute(accountID, prefix, peerID string, peerGroupIDs []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, accessControlGroupIDs []string, enabled bool, userID string) (*route.Route, error) SaveRoute(accountID, userID string, route *route.Route) error DeleteRoute(accountID string, routeID route.ID, userID string) error ListRoutes(accountID, userID string) ([]*route.Route, error) diff --git a/management/server/account_test.go b/management/server/account_test.go index 8c4219da7cf..ed3ef6e30b0 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -1576,9 +1576,10 @@ func TestAccount_Copy(t *testing.T) { }, Routes: map[route.ID]*route.Route{ "route1": { - ID: "route1", - PeerGroups: []string{}, - Groups: []string{"group1"}, + ID: "route1", + PeerGroups: []string{}, + Groups: []string{"group1"}, + AccessControlGroups: []string{}, }, }, NameServerGroups: map[string]*nbdns.NameServerGroup{ diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index aeaef6f6412..df8fd64ef43 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -1014,6 +1014,12 @@ components: items: type: string example: "chacdk86lnnboviihd70" + access_control_groups: + description: Access control group identifier associated with route. + type: array + items: + type: string + example: "chacbco6lnnbn6cg5s91" required: - id - description diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index e378213a116..a54519b1a9d 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -951,6 +951,9 @@ type PostureCheckUpdate struct { // Route defines model for Route. type Route struct { + // AccessControlGroups Access control group identifier associated with route. + AccessControlGroups *[]string `json:"access_control_groups,omitempty"` + // Description Route description Description string `json:"description"` @@ -987,6 +990,9 @@ type Route struct { // RouteRequest defines model for RouteRequest. type RouteRequest struct { + // AccessControlGroups Access control group identifier associated with route. + AccessControlGroups *[]string `json:"access_control_groups,omitempty"` + // Description Route description Description string `json:"description"` diff --git a/management/server/http/routes_handler.go b/management/server/http/routes_handler.go index f755e7a16a2..d5b94693fdc 100644 --- a/management/server/http/routes_handler.go +++ b/management/server/http/routes_handler.go @@ -87,11 +87,16 @@ func (h *RoutesHandler) CreateRoute(w http.ResponseWriter, r *http.Request) { peerId = *req.Peer } - peerGroupIds := []string{} + var peerGroupIds []string if req.PeerGroups != nil { peerGroupIds = *req.PeerGroups } + var accessControlGroupIds []string + if req.AccessControlGroups != nil { + accessControlGroupIds = *req.AccessControlGroups + } + if (peerId != "" && len(peerGroupIds) > 0) || (peerId == "" && len(peerGroupIds) == 0) { util.WriteError(status.Errorf(status.InvalidArgument, "only one peer or peer_groups should be provided"), w) return @@ -107,7 +112,7 @@ func (h *RoutesHandler) CreateRoute(w http.ResponseWriter, r *http.Request) { newRoute, err := h.accountManager.CreateRoute( account.Id, newPrefix.String(), peerId, peerGroupIds, - req.Description, route.NetID(req.NetworkId), req.Masquerade, req.Metric, req.Groups, req.Enabled, user.Id, + req.Description, route.NetID(req.NetworkId), req.Masquerade, req.Metric, req.Groups, accessControlGroupIds, req.Enabled, user.Id, ) if err != nil { util.WriteError(err, w) @@ -204,6 +209,10 @@ func (h *RoutesHandler) UpdateRoute(w http.ResponseWriter, r *http.Request) { newRoute.PeerGroups = *req.PeerGroups } + if req.AccessControlGroups != nil { + newRoute.AccessControlGroups = *req.AccessControlGroups + } + err = h.accountManager.SaveRoute(account.Id, user.Id, newRoute) if err != nil { util.WriteError(err, w) @@ -280,5 +289,8 @@ func toRouteResponse(serverRoute *route.Route) *api.Route { if len(serverRoute.PeerGroups) > 0 { route.PeerGroups = &serverRoute.PeerGroups } + if len(serverRoute.AccessControlGroups) > 0 { + route.AccessControlGroups = &serverRoute.AccessControlGroups + } return route } diff --git a/management/server/http/routes_handler_test.go b/management/server/http/routes_handler_test.go index 1c8288d5f7f..32f46da75d7 100644 --- a/management/server/http/routes_handler_test.go +++ b/management/server/http/routes_handler_test.go @@ -93,7 +93,7 @@ func initRoutesTestData() *RoutesHandler { } return nil, status.Errorf(status.NotFound, "route with ID %s not found", routeID) }, - CreateRouteFunc: func(accountID, network, peerID string, peerGroups []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, enabled bool, _ string) (*route.Route, error) { + CreateRouteFunc: func(accountID, network, peerID string, peerGroups []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, accessControlGroups []string, enabled bool, _ string) (*route.Route, error) { if peerID == notFoundPeerID { return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID) } @@ -102,16 +102,17 @@ func initRoutesTestData() *RoutesHandler { } networkType, p, _ := route.ParseNetwork(network) return &route.Route{ - ID: existingRouteID, - NetID: netID, - Peer: peerID, - PeerGroups: peerGroups, - Network: p, - NetworkType: networkType, - Description: description, - Masquerade: masquerade, - Enabled: enabled, - Groups: groups, + ID: existingRouteID, + NetID: netID, + Peer: peerID, + PeerGroups: peerGroups, + Network: p, + NetworkType: networkType, + Description: description, + Masquerade: masquerade, + Enabled: enabled, + Groups: groups, + AccessControlGroups: accessControlGroups, }, nil }, SaveRouteFunc: func(_, _ string, r *route.Route) error { @@ -210,6 +211,27 @@ func TestRoutesHandlers(t *testing.T) { Groups: []string{existingGroupID}, }, }, + { + name: "POST OK With Access Control Groups", + requestType: http.MethodPost, + requestPath: "/api/routes", + requestBody: bytes.NewBuffer( + []byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"],\"access_control_groups\":[\"%s\"]}", existingPeerID, existingGroupID, existingGroupID))), + expectedStatus: http.StatusOK, + expectedBody: true, + expectedRoute: &api.Route{ + Id: existingRouteID, + Description: "Post", + NetworkId: "awesomeNet", + Network: "192.168.0.0/16", + Peer: &existingPeerID, + NetworkType: route.IPv4NetworkString, + Masquerade: false, + Enabled: false, + Groups: []string{existingGroupID}, + AccessControlGroups: &[]string{existingGroupID}, + }, + }, { name: "POST Non Linux Peer", requestType: http.MethodPost, @@ -279,6 +301,26 @@ func TestRoutesHandlers(t *testing.T) { Groups: []string{existingGroupID}, }, }, + { + name: "PUT OK With Access Control Groups", + requestType: http.MethodPut, + requestPath: "/api/routes/" + existingRouteID, + requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"],\"access_control_groups\":[\"%s\"]}}", existingPeerID, existingGroupID, existingGroupID)), + expectedStatus: http.StatusOK, + expectedBody: true, + expectedRoute: &api.Route{ + Id: existingRouteID, + Description: "Post", + NetworkId: "awesomeNet", + Network: "192.168.0.0/16", + Peer: &existingPeerID, + NetworkType: route.IPv4NetworkString, + Masquerade: false, + Enabled: false, + Groups: []string{existingGroupID}, + AccessControlGroups: &[]string{existingGroupID}, + }, + }, { name: "PUT OK when peer_groups provided", requestType: http.MethodPut, diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 259bd645df7..cc5cf1f64e9 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -52,7 +52,7 @@ type MockAccountManager struct { UpdatePeerMetaFunc func(peerID string, meta nbpeer.PeerSystemMeta) error UpdatePeerSSHKeyFunc func(peerID string, sshKey string) error UpdatePeerFunc func(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error) - CreateRouteFunc func(accountID, prefix, peer string, peerGroups []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) + CreateRouteFunc func(accountID, prefix, peer string, peerGroups []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, accessControlGroups []string, enabled bool, userID string) (*route.Route, error) GetRouteFunc func(accountID string, routeID route.ID, userID string) (*route.Route, error) SaveRouteFunc func(accountID string, userID string, route *route.Route) error DeleteRouteFunc func(accountID string, routeID route.ID, userID string) error @@ -412,9 +412,9 @@ func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *nbpeer. } // CreateRoute mock implementation of CreateRoute from server.AccountManager interface -func (am *MockAccountManager) CreateRoute(accountID, prefix, peerID string, peerGroupIDs []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) { +func (am *MockAccountManager) CreateRoute(accountID, prefix, peerID string, peerGroupIDs []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, accessControlGroups []string, enabled bool, userID string) (*route.Route, error) { if am.CreateRouteFunc != nil { - return am.CreateRouteFunc(accountID, prefix, peerID, peerGroupIDs, description, netID, masquerade, metric, groups, enabled, userID) + return am.CreateRouteFunc(accountID, prefix, peerID, peerGroupIDs, description, netID, masquerade, metric, groups, accessControlGroups, enabled, userID) } return nil, status.Errorf(codes.Unimplemented, "method CreateRoute is not implemented") } diff --git a/management/server/route.go b/management/server/route.go index 2de813d48ba..dcead97ca40 100644 --- a/management/server/route.go +++ b/management/server/route.go @@ -114,7 +114,7 @@ func (am *DefaultAccountManager) checkRoutePrefixExistsForPeers(account *Account } // CreateRoute creates and saves a new route -func (am *DefaultAccountManager) CreateRoute(accountID, network, peerID string, peerGroupIDs []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) { +func (am *DefaultAccountManager) CreateRoute(accountID, network, peerID string, peerGroupIDs []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, accessControlGroupIDs []string, enabled bool, userID string) (*route.Route, error) { unlock := am.Store.AcquireAccountWriteLock(accountID) defer unlock() @@ -145,6 +145,13 @@ func (am *DefaultAccountManager) CreateRoute(accountID, network, peerID string, } } + if len(accessControlGroupIDs) > 0 { + err = validateGroups(accessControlGroupIDs, account.Groups) + if err != nil { + return nil, err + } + } + err = am.checkRoutePrefixExistsForPeers(account, peerID, newRoute.ID, peerGroupIDs, newPrefix) if err != nil { return nil, err @@ -173,6 +180,7 @@ func (am *DefaultAccountManager) CreateRoute(accountID, network, peerID string, newRoute.Metric = metric newRoute.Enabled = enabled newRoute.Groups = groups + newRoute.AccessControlGroups = accessControlGroupIDs if account.Routes == nil { account.Routes = make(map[route.ID]*route.Route) @@ -229,6 +237,13 @@ func (am *DefaultAccountManager) SaveRoute(accountID, userID string, routeToSave } } + if len(routeToSave.AccessControlGroups) > 0 { + err = validateGroups(routeToSave.AccessControlGroups, account.Groups) + if err != nil { + return err + } + } + err = am.checkRoutePrefixExistsForPeers(account, routeToSave.Peer, routeToSave.ID, routeToSave.Copy().PeerGroups, routeToSave.Network) if err != nil { return err diff --git a/management/server/route_test.go b/management/server/route_test.go index d28b40d48ed..0b51c591ff2 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -39,15 +39,16 @@ const ( func TestCreateRoute(t *testing.T) { type input struct { - network string - netID route.NetID - peerKey string - peerGroupIDs []string - description string - masquerade bool - metric int - enabled bool - groups []string + network string + netID route.NetID + peerKey string + peerGroupIDs []string + description string + masquerade bool + metric int + enabled bool + groups []string + accessControlGroups []string } testCases := []struct { @@ -61,67 +62,72 @@ func TestCreateRoute(t *testing.T) { { name: "Happy Path", inputArgs: input{ - network: "192.168.0.0/16", - netID: "happy", - peerKey: peer1ID, - description: "super", - masquerade: false, - metric: 9999, - enabled: true, - groups: []string{routeGroup1}, + network: "192.168.0.0/16", + netID: "happy", + peerKey: peer1ID, + description: "super", + masquerade: false, + metric: 9999, + enabled: true, + groups: []string{routeGroup1}, + accessControlGroups: []string{routeGroup1}, }, errFunc: require.NoError, shouldCreate: true, expectedRoute: &route.Route{ - Network: netip.MustParsePrefix("192.168.0.0/16"), - NetworkType: route.IPv4Network, - NetID: "happy", - Peer: peer1ID, - Description: "super", - Masquerade: false, - Metric: 9999, - Enabled: true, - Groups: []string{routeGroup1}, + Network: netip.MustParsePrefix("192.168.0.0/16"), + NetworkType: route.IPv4Network, + NetID: "happy", + Peer: peer1ID, + Description: "super", + Masquerade: false, + Metric: 9999, + Enabled: true, + Groups: []string{routeGroup1}, + AccessControlGroups: []string{routeGroup1}, }, }, { name: "Happy Path Peer Groups", inputArgs: input{ - network: "192.168.0.0/16", - netID: "happy", - peerGroupIDs: []string{routeGroupHA1, routeGroupHA2}, - description: "super", - masquerade: false, - metric: 9999, - enabled: true, - groups: []string{routeGroup1, routeGroup2}, + network: "192.168.0.0/16", + netID: "happy", + peerGroupIDs: []string{routeGroupHA1, routeGroupHA2}, + description: "super", + masquerade: false, + metric: 9999, + enabled: true, + groups: []string{routeGroup1, routeGroup2}, + accessControlGroups: []string{routeGroup1, routeGroup2}, }, errFunc: require.NoError, shouldCreate: true, expectedRoute: &route.Route{ - Network: netip.MustParsePrefix("192.168.0.0/16"), - NetworkType: route.IPv4Network, - NetID: "happy", - PeerGroups: []string{routeGroupHA1, routeGroupHA2}, - Description: "super", - Masquerade: false, - Metric: 9999, - Enabled: true, - Groups: []string{routeGroup1, routeGroup2}, + Network: netip.MustParsePrefix("192.168.0.0/16"), + NetworkType: route.IPv4Network, + NetID: "happy", + PeerGroups: []string{routeGroupHA1, routeGroupHA2}, + Description: "super", + Masquerade: false, + Metric: 9999, + Enabled: true, + Groups: []string{routeGroup1, routeGroup2}, + AccessControlGroups: []string{routeGroup1, routeGroup2}, }, }, { name: "Both peer and peer_groups Provided Should Fail", inputArgs: input{ - network: "192.168.0.0/16", - netID: "happy", - peerKey: peer1ID, - peerGroupIDs: []string{routeGroupHA1}, - description: "super", - masquerade: false, - metric: 9999, - enabled: true, - groups: []string{routeGroup1}, + network: "192.168.0.0/16", + netID: "happy", + peerKey: peer1ID, + peerGroupIDs: []string{routeGroupHA1}, + description: "super", + masquerade: false, + metric: 9999, + enabled: true, + groups: []string{routeGroup1}, + accessControlGroups: []string{routeGroup2}, }, errFunc: require.Error, shouldCreate: false, @@ -129,14 +135,15 @@ func TestCreateRoute(t *testing.T) { { name: "Bad Prefix Should Fail", inputArgs: input{ - network: "192.168.0.0/34", - netID: "happy", - peerKey: peer1ID, - description: "super", - masquerade: false, - metric: 9999, - enabled: true, - groups: []string{routeGroup1}, + network: "192.168.0.0/34", + netID: "happy", + peerKey: peer1ID, + description: "super", + masquerade: false, + metric: 9999, + enabled: true, + groups: []string{routeGroup1}, + accessControlGroups: []string{routeGroup2}, }, errFunc: require.Error, shouldCreate: false, @@ -338,7 +345,7 @@ func TestCreateRoute(t *testing.T) { t.Errorf("failed to get group all: %s", errInit) } _, errInit = am.CreateRoute(account.Id, existingNetwork, "", []string{routeGroup3, routeGroup4}, - "", existingRouteID, false, 1000, []string{groupAll.ID}, true, userID) + "", existingRouteID, false, 1000, []string{groupAll.ID}, []string{groupAll.ID}, true, userID) if errInit != nil { t.Errorf("failed to create init route: %s", errInit) } @@ -354,6 +361,7 @@ func TestCreateRoute(t *testing.T) { testCase.inputArgs.masquerade, testCase.inputArgs.metric, testCase.inputArgs.groups, + testCase.inputArgs.accessControlGroups, testCase.inputArgs.enabled, userID, ) @@ -814,15 +822,16 @@ func TestDeleteRoute(t *testing.T) { func TestGetNetworkMap_RouteSyncPeerGroups(t *testing.T) { baseRoute := &route.Route{ - Network: netip.MustParsePrefix("192.168.0.0/16"), - NetID: "superNet", - NetworkType: route.IPv4Network, - PeerGroups: []string{routeGroupHA1, routeGroupHA2}, - Description: "ha route", - Masquerade: false, - Metric: 9999, - Enabled: true, - Groups: []string{routeGroup1, routeGroup2}, + Network: netip.MustParsePrefix("192.168.0.0/16"), + NetID: "superNet", + NetworkType: route.IPv4Network, + PeerGroups: []string{routeGroupHA1, routeGroupHA2}, + Description: "ha route", + Masquerade: false, + Metric: 9999, + Enabled: true, + Groups: []string{routeGroup1, routeGroup2}, + AccessControlGroups: []string{routeGroup1}, } am, err := createRouterManager(t) @@ -841,7 +850,8 @@ func TestGetNetworkMap_RouteSyncPeerGroups(t *testing.T) { newRoute, err := am.CreateRoute( account.Id, baseRoute.Network.String(), baseRoute.Peer, baseRoute.PeerGroups, baseRoute.Description, - baseRoute.NetID, baseRoute.Masquerade, baseRoute.Metric, baseRoute.Groups, baseRoute.Enabled, userID) + baseRoute.NetID, baseRoute.Masquerade, baseRoute.Metric, baseRoute.Groups, baseRoute.AccessControlGroups, + baseRoute.Enabled, userID) require.NoError(t, err) require.Equal(t, newRoute.Enabled, true) @@ -906,16 +916,17 @@ func TestGetNetworkMap_RouteSync(t *testing.T) { // no routes for peer in different groups // no routes when route is deleted baseRoute := &route.Route{ - ID: "testingRoute", - Network: netip.MustParsePrefix("192.168.0.0/16"), - NetID: "superNet", - NetworkType: route.IPv4Network, - Peer: peer1ID, - Description: "super", - Masquerade: false, - Metric: 9999, - Enabled: true, - Groups: []string{routeGroup1}, + ID: "testingRoute", + Network: netip.MustParsePrefix("192.168.0.0/16"), + NetID: "superNet", + NetworkType: route.IPv4Network, + Peer: peer1ID, + Description: "super", + Masquerade: false, + Metric: 9999, + Enabled: true, + Groups: []string{routeGroup1}, + AccessControlGroups: []string{routeGroup1}, } am, err := createRouterManager(t) @@ -933,8 +944,8 @@ func TestGetNetworkMap_RouteSync(t *testing.T) { require.Len(t, newAccountRoutes.Routes, 0, "new accounts should have no routes") createdRoute, err := am.CreateRoute(account.Id, baseRoute.Network.String(), peer1ID, []string{}, - baseRoute.Description, baseRoute.NetID, baseRoute.Masquerade, baseRoute.Metric, baseRoute.Groups, false, - userID) + baseRoute.Description, baseRoute.NetID, baseRoute.Masquerade, baseRoute.Metric, baseRoute.Groups, + baseRoute.AccessControlGroups, false, userID) require.NoError(t, err) noDisabledRoutes, err := am.GetNetworkMap(peer1ID) diff --git a/route/route.go b/route/route.go index 50c53cbe6da..8cf313783b8 100644 --- a/route/route.go +++ b/route/route.go @@ -73,17 +73,18 @@ func ToPrefixType(prefix string) NetworkType { type Route struct { ID ID `gorm:"primaryKey"` // AccountID is a reference to Account that this object belongs - AccountID string `gorm:"index"` - Network netip.Prefix `gorm:"serializer:json"` - NetID NetID - Description string - Peer string - PeerGroups []string `gorm:"serializer:json"` - NetworkType NetworkType - Masquerade bool - Metric int - Enabled bool - Groups []string `gorm:"serializer:json"` + AccountID string `gorm:"index"` + Network netip.Prefix `gorm:"serializer:json"` + NetID NetID + Description string + Peer string + PeerGroups []string `gorm:"serializer:json"` + NetworkType NetworkType + Masquerade bool + Metric int + Enabled bool + Groups []string `gorm:"serializer:json"` + AccessControlGroups []string `gorm:"serializer:json"` } // EventMeta returns activity event meta related to the route @@ -94,20 +95,22 @@ func (r *Route) EventMeta() map[string]any { // Copy copies a route object func (r *Route) Copy() *Route { route := &Route{ - ID: r.ID, - Description: r.Description, - NetID: r.NetID, - Network: r.Network, - NetworkType: r.NetworkType, - Peer: r.Peer, - PeerGroups: make([]string, len(r.PeerGroups)), - Metric: r.Metric, - Masquerade: r.Masquerade, - Enabled: r.Enabled, - Groups: make([]string, len(r.Groups)), + ID: r.ID, + Description: r.Description, + NetID: r.NetID, + Network: r.Network, + NetworkType: r.NetworkType, + Peer: r.Peer, + PeerGroups: make([]string, len(r.PeerGroups)), + Metric: r.Metric, + Masquerade: r.Masquerade, + Enabled: r.Enabled, + Groups: make([]string, len(r.Groups)), + AccessControlGroups: make([]string, len(r.AccessControlGroups)), } copy(route.Groups, r.Groups) copy(route.PeerGroups, r.PeerGroups) + copy(route.AccessControlGroups, r.AccessControlGroups) return route } @@ -129,7 +132,8 @@ func (r *Route) IsEqual(other *Route) bool { other.Masquerade == r.Masquerade && other.Enabled == r.Enabled && compareList(r.Groups, other.Groups) && - compareList(r.PeerGroups, other.PeerGroups) + compareList(r.PeerGroups, other.PeerGroups) && + compareList(r.AccessControlGroups, other.AccessControlGroups) } // ParseNetwork Parses a network prefix string and returns a netip.Prefix object and if is invalid, IPv4 or IPv6