mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 16:26:38 +00:00
[management, client] Add access control support to network routes (#2100)
This commit is contained in:
@@ -727,17 +727,39 @@ components:
|
||||
enum: ["all", "tcp", "udp", "icmp"]
|
||||
example: "tcp"
|
||||
ports:
|
||||
description: Policy rule affected ports or it ranges list
|
||||
description: Policy rule affected ports
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: "80"
|
||||
port_ranges:
|
||||
description: Policy rule affected ports ranges list
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RulePortRange'
|
||||
required:
|
||||
- name
|
||||
- enabled
|
||||
- bidirectional
|
||||
- protocol
|
||||
- action
|
||||
|
||||
RulePortRange:
|
||||
description: Policy rule affected ports range
|
||||
type: object
|
||||
properties:
|
||||
start:
|
||||
description: The starting port of the range
|
||||
type: integer
|
||||
example: 80
|
||||
end:
|
||||
description: The ending port of the range
|
||||
type: integer
|
||||
example: 320
|
||||
required:
|
||||
- start
|
||||
- end
|
||||
|
||||
PolicyRuleUpdate:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/PolicyRuleMinimum'
|
||||
@@ -1106,6 +1128,12 @@ components:
|
||||
description: Indicate if the route should be kept after a domain doesn't resolve that IP anymore
|
||||
type: boolean
|
||||
example: true
|
||||
access_control_groups:
|
||||
description: Access control group identifier associated with route.
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: "chacbco6lnnbn6cg5s91"
|
||||
required:
|
||||
- id
|
||||
- description
|
||||
|
||||
@@ -780,7 +780,10 @@ type PolicyRule struct {
|
||||
// Name Policy rule name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// Ports Policy rule affected ports or it ranges list
|
||||
// PortRanges Policy rule affected ports ranges list
|
||||
PortRanges *[]RulePortRange `json:"port_ranges,omitempty"`
|
||||
|
||||
// Ports Policy rule affected ports
|
||||
Ports *[]string `json:"ports,omitempty"`
|
||||
|
||||
// Protocol Policy rule type of the traffic
|
||||
@@ -816,7 +819,10 @@ type PolicyRuleMinimum struct {
|
||||
// Name Policy rule name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// Ports Policy rule affected ports or it ranges list
|
||||
// PortRanges Policy rule affected ports ranges list
|
||||
PortRanges *[]RulePortRange `json:"port_ranges,omitempty"`
|
||||
|
||||
// Ports Policy rule affected ports
|
||||
Ports *[]string `json:"ports,omitempty"`
|
||||
|
||||
// Protocol Policy rule type of the traffic
|
||||
@@ -852,7 +858,10 @@ type PolicyRuleUpdate struct {
|
||||
// Name Policy rule name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// Ports Policy rule affected ports or it ranges list
|
||||
// PortRanges Policy rule affected ports ranges list
|
||||
PortRanges *[]RulePortRange `json:"port_ranges,omitempty"`
|
||||
|
||||
// Ports Policy rule affected ports
|
||||
Ports *[]string `json:"ports,omitempty"`
|
||||
|
||||
// Protocol Policy rule type of the traffic
|
||||
@@ -935,6 +944,9 @@ type ProcessCheck 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"`
|
||||
|
||||
@@ -977,6 +989,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"`
|
||||
|
||||
@@ -1011,6 +1026,15 @@ type RouteRequest struct {
|
||||
PeerGroups *[]string `json:"peer_groups,omitempty"`
|
||||
}
|
||||
|
||||
// RulePortRange Policy rule affected ports range
|
||||
type RulePortRange struct {
|
||||
// End The ending port of the range
|
||||
End int `json:"end"`
|
||||
|
||||
// Start The starting port of the range
|
||||
Start int `json:"start"`
|
||||
}
|
||||
|
||||
// SetupKey defines model for SetupKey.
|
||||
type SetupKey struct {
|
||||
// AutoGroups List of group IDs to auto-assign to peers registered with this key
|
||||
|
||||
@@ -172,6 +172,11 @@ func (h *Policies) savePolicy(w http.ResponseWriter, r *http.Request, accountID
|
||||
return
|
||||
}
|
||||
|
||||
if (rule.Ports != nil && len(*rule.Ports) != 0) && (rule.PortRanges != nil && len(*rule.PortRanges) != 0) {
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "specify either individual ports or port ranges, not both"), w)
|
||||
return
|
||||
}
|
||||
|
||||
if rule.Ports != nil && len(*rule.Ports) != 0 {
|
||||
for _, v := range *rule.Ports {
|
||||
if port, err := strconv.Atoi(v); err != nil || port < 1 || port > 65535 {
|
||||
@@ -182,10 +187,23 @@ func (h *Policies) savePolicy(w http.ResponseWriter, r *http.Request, accountID
|
||||
}
|
||||
}
|
||||
|
||||
if rule.PortRanges != nil && len(*rule.PortRanges) != 0 {
|
||||
for _, portRange := range *rule.PortRanges {
|
||||
if portRange.Start < 1 || portRange.End > 65535 {
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "valid port value is in 1..65535 range"), w)
|
||||
return
|
||||
}
|
||||
pr.PortRanges = append(pr.PortRanges, server.RulePortRange{
|
||||
Start: uint16(portRange.Start),
|
||||
End: uint16(portRange.End),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// validate policy object
|
||||
switch pr.Protocol {
|
||||
case server.PolicyRuleProtocolALL, server.PolicyRuleProtocolICMP:
|
||||
if len(pr.Ports) != 0 {
|
||||
if len(pr.Ports) != 0 || len(pr.PortRanges) != 0 {
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "for ALL or ICMP protocol ports is not allowed"), w)
|
||||
return
|
||||
}
|
||||
@@ -194,7 +212,7 @@ func (h *Policies) savePolicy(w http.ResponseWriter, r *http.Request, accountID
|
||||
return
|
||||
}
|
||||
case server.PolicyRuleProtocolTCP, server.PolicyRuleProtocolUDP:
|
||||
if !pr.Bidirectional && len(pr.Ports) == 0 {
|
||||
if !pr.Bidirectional && (len(pr.Ports) == 0 || len(pr.PortRanges) != 0) {
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "for ALL or ICMP protocol type flow can be only bi-directional"), w)
|
||||
return
|
||||
}
|
||||
@@ -320,6 +338,17 @@ func toPolicyResponse(groups []*nbgroup.Group, policy *server.Policy) *api.Polic
|
||||
rule.Ports = &portsCopy
|
||||
}
|
||||
|
||||
if len(r.PortRanges) != 0 {
|
||||
portRanges := make([]api.RulePortRange, 0, len(r.PortRanges))
|
||||
for _, portRange := range r.PortRanges {
|
||||
portRanges = append(portRanges, api.RulePortRange{
|
||||
End: int(portRange.End),
|
||||
Start: int(portRange.Start),
|
||||
})
|
||||
}
|
||||
rule.PortRanges = &portRanges
|
||||
}
|
||||
|
||||
for _, gid := range r.Sources {
|
||||
_, ok := cache[gid]
|
||||
if ok {
|
||||
|
||||
@@ -117,9 +117,14 @@ func (h *RoutesHandler) CreateRoute(w http.ResponseWriter, r *http.Request) {
|
||||
peerGroupIds = *req.PeerGroups
|
||||
}
|
||||
|
||||
var accessControlGroupIds []string
|
||||
if req.AccessControlGroups != nil {
|
||||
accessControlGroupIds = *req.AccessControlGroups
|
||||
}
|
||||
|
||||
newRoute, err := h.accountManager.CreateRoute(r.Context(), accountID, newPrefix, networkType, domains, peerId, peerGroupIds,
|
||||
req.Description, route.NetID(req.NetworkId), req.Masquerade, req.Metric, req.Groups, req.Enabled, userID, req.KeepRoute,
|
||||
)
|
||||
req.Description, route.NetID(req.NetworkId), req.Masquerade, req.Metric, req.Groups, accessControlGroupIds, req.Enabled, userID, req.KeepRoute)
|
||||
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
@@ -233,6 +238,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(r.Context(), accountID, userID, newRoute)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
@@ -326,6 +335,9 @@ func toRouteResponse(serverRoute *route.Route) (*api.Route, error) {
|
||||
if len(serverRoute.PeerGroups) > 0 {
|
||||
route.PeerGroups = &serverRoute.PeerGroups
|
||||
}
|
||||
if len(serverRoute.AccessControlGroups) > 0 {
|
||||
route.AccessControlGroups = &serverRoute.AccessControlGroups
|
||||
}
|
||||
return route, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ func initRoutesTestData() *RoutesHandler {
|
||||
}
|
||||
return nil, status.Errorf(status.NotFound, "route with ID %s not found", routeID)
|
||||
},
|
||||
CreateRouteFunc: func(_ context.Context, accountID string, prefix netip.Prefix, networkType route.NetworkType, domains domain.List, peerID string, peerGroups []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, enabled bool, _ string, keepRoute bool) (*route.Route, error) {
|
||||
CreateRouteFunc: func(_ context.Context, accountID string, prefix netip.Prefix, networkType route.NetworkType, domains domain.List, peerID string, peerGroups []string, description string, netID route.NetID, masquerade bool, metric int, groups, accessControlGroups []string, enabled bool, _ string, keepRoute bool) (*route.Route, error) {
|
||||
if peerID == notFoundPeerID {
|
||||
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
||||
}
|
||||
@@ -119,18 +119,19 @@ func initRoutesTestData() *RoutesHandler {
|
||||
}
|
||||
|
||||
return &route.Route{
|
||||
ID: existingRouteID,
|
||||
NetID: netID,
|
||||
Peer: peerID,
|
||||
PeerGroups: peerGroups,
|
||||
Network: prefix,
|
||||
Domains: domains,
|
||||
NetworkType: networkType,
|
||||
Description: description,
|
||||
Masquerade: masquerade,
|
||||
Enabled: enabled,
|
||||
Groups: groups,
|
||||
KeepRoute: keepRoute,
|
||||
ID: existingRouteID,
|
||||
NetID: netID,
|
||||
Peer: peerID,
|
||||
PeerGroups: peerGroups,
|
||||
Network: prefix,
|
||||
Domains: domains,
|
||||
NetworkType: networkType,
|
||||
Description: description,
|
||||
Masquerade: masquerade,
|
||||
Enabled: enabled,
|
||||
Groups: groups,
|
||||
KeepRoute: keepRoute,
|
||||
AccessControlGroups: accessControlGroups,
|
||||
}, nil
|
||||
},
|
||||
SaveRouteFunc: func(_ context.Context, _, _ string, r *route.Route) error {
|
||||
@@ -268,6 +269,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: toPtr("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,
|
||||
|
||||
Reference in New Issue
Block a user