Feat linux firewall support (#805)

Update the client's engine to apply firewall rules received from the manager (results of ACL policy).
This commit is contained in:
Givi Khojanashvili
2023-05-29 18:00:18 +04:00
committed by GitHub
parent 2eb9a97fee
commit ba7a39a4fc
51 changed files with 4143 additions and 1013 deletions

View File

@@ -551,49 +551,91 @@ components:
required:
- sources
- destinations
PolicyRule:
PolicyRuleMinimum:
type: object
properties:
id:
description: Rule ID
description: Policy rule ID
type: string
example: ch8i4ug6lnn4g9hqv7mg
name:
description: Rule name identifier
description: Policy rule name identifier
type: string
example: Default
description:
description: Rule friendly description
description: Policy rule friendly description
type: string
example: This is a default rule that allows connections between all the resources
enabled:
description: Rules status
description: Policy rule status
type: boolean
example: true
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
description: Policy rule accept or drops packets
type: string
enum: ["accept","drop"]
example: accept
bidirectional:
description: Define if the rule is applicable in both directions, sources, and destinations.
type: boolean
example: true
protocol:
description: Policy rule type of the traffic
type: string
enum: ["all", "tcp", "udp", "icmp"]
example: "tcp"
ports:
description: Policy rule affected ports or it ranges list
type: array
items:
type: string
example: [80,443]
required:
- name
- sources
- destinations
- action
- enabled
- bidirectional
- protocol
- action
PolicyRuleUpdate:
allOf:
- $ref: '#/components/schemas/PolicyRuleMinimum'
- type: object
properties:
sources:
description: Policy rule source groups
type: array
items:
type: string
destinations:
description: Policy rule destination groups
type: array
items:
type: string
required:
- sources
- destinations
PolicyRule:
allOf:
- $ref: '#/components/schemas/PolicyRuleMinimum'
- type: object
properties:
sources:
description: Policy rule source groups
type: array
items:
$ref: '#/components/schemas/GroupMinimum'
destinations:
description: Policy rule destination groups
type: array
items:
$ref: '#/components/schemas/GroupMinimum'
required:
- sources
- destinations
PolicyMinimum:
type: object
properties:
id:
description: Policy ID
type: string
name:
description: Policy name identifier
type: string
@@ -609,29 +651,35 @@ components:
query:
description: Policy Rego query
type: string
example: package netbird\n\nall[rule] {\n is_peer_in_any_group([\"ch8i4ug6lnn4g9hqv7m0\",\"ch8i4ug6lnn4g9hqv7m0\"])\n rule := {\n rules_from_group(\"ch8i4ug6lnn4g9hqv7m0\", \"dst\", \"accept\", \"\"),\n rules_from_group(\"ch8i4ug6lnn4g9hqv7m0\", \"src\", \"accept\", \"\"),\n }[_][_]\n}\n
rules:
description: Policy rule object for policy UI editor
type: array
items:
$ref: '#/components/schemas/PolicyRule'
required:
- name
- description
- enabled
- query
- rules
PolicyUpdate:
allOf:
- $ref: '#/components/schemas/PolicyMinimum'
- type: object
properties:
rules:
description: Policy rule object for policy UI editor
type: array
items:
$ref: '#/components/schemas/PolicyRuleUpdate'
required:
- rules
Policy:
allOf:
- $ref: '#/components/schemas/PolicyMinimum'
- type: object
properties:
id:
description: Policy ID
type: string
example: ch8i4ug6lnn4g9hqv7mg
rules:
description: Policy rule object for policy UI editor
type: array
items:
$ref: '#/components/schemas/PolicyRule'
required:
- id
- rules
RouteRequest:
type: object
properties:
@@ -884,7 +932,7 @@ security:
paths:
/api/accounts:
get:
summary: List all Accounts
summary: List all accounts
description: Returns a list of accounts of a user. Always returns a list of one account.
tags: [ Accounts ]
security:
@@ -909,7 +957,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/accounts/{accountId}:
put:
summary: Update an Account
summary: Update an account
description: Update information about an account
tags: [ Accounts ]
security:
@@ -950,7 +998,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/users:
get:
summary: List all Users
summary: List all users
description: Returns a list of all users
tags: [ Users ]
security:
@@ -980,7 +1028,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a User
summary: Create a user
description: Creates a new service user or sends an invite to a regular user
tags: [ Users ]
security:
@@ -1009,7 +1057,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/users/{userId}:
put:
summary: Update a User
summary: Update a user
description: Update information about a User
tags: [ Users ]
security:
@@ -1044,8 +1092,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a User
description: Delete a User
summary: Delete a user
description: Delete a user
tags: [ Users ]
security:
- BearerAuth: [ ]
@@ -1071,7 +1119,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/users/{userId}/tokens:
get:
summary: List all Tokens
summary: List all tokens
description: Returns a list of all tokens for a user
tags: [ Tokens ]
security:
@@ -1102,7 +1150,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Token
summary: Create a token
description: Create a new token for a user
tags: [ Tokens ]
security:
@@ -1138,7 +1186,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/users/{userId}/tokens/{tokenId}:
get:
summary: Retrieve a Token
summary: Retrieve a token
description: Returns a specific token for a user
tags: [ Tokens ]
security:
@@ -1173,7 +1221,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Token
summary: Delete a token
description: Delete a token for a user
tags: [ Tokens ]
security:
@@ -1206,7 +1254,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/peers:
get:
summary: List all Peers
summary: List all peers
description: Returns a list of all peers
tags: [ Peers ]
security:
@@ -1231,7 +1279,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/peers/{peerId}:
get:
summary: Retrieve a Peer
summary: Retrieve a peer
description: Get information about a peer
tags: [ Peers ]
security:
@@ -1260,7 +1308,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Peer
summary: Update a peer
description: Update information about a peer
tags: [ Peers ]
security:
@@ -1295,7 +1343,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Peer
summary: Delete a peer
description: Delete a peer
tags: [ Peers ]
security:
@@ -1322,7 +1370,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/setup-keys:
get:
summary: List all Setup Keys
summary: List all setup keys
description: Returns a list of all Setup Keys
tags: [ Setup Keys ]
security:
@@ -1346,8 +1394,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Setup Key
description: Creates a Setup Key
summary: Create a setup key
description: Creates a setup key
tags: [ Setup Keys ]
security:
- BearerAuth: [ ]
@@ -1375,8 +1423,8 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/setup-keys/{keyId}:
get:
summary: Retrieve a Setup Key
description: Get information about a Setup Key
summary: Retrieve a setup key
description: Get information about a setup key
tags: [ Setup Keys ]
security:
- BearerAuth: [ ]
@@ -1404,8 +1452,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Setup Key
description: Update information about a Setup Key
summary: Update a setup key
description: Update information about a setup key
tags: [ Setup Keys ]
security:
- BearerAuth: [ ]
@@ -1440,8 +1488,8 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/groups:
get:
summary: List all Groups
description: Returns a list of all Groups
summary: List all groups
description: Returns a list of all groups
tags: [ Groups ]
security:
- BearerAuth: [ ]
@@ -1464,8 +1512,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Group
description: Creates a Group
summary: Create a group
description: Creates a group
tags: [ Groups ]
security:
- BearerAuth: [ ]
@@ -1493,8 +1541,8 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/groups/{groupId}:
get:
summary: Retrieve a Group
description: Get information about a Group
summary: Retrieve a group
description: Get information about a group
tags: [ Groups ]
security:
- BearerAuth: [ ]
@@ -1522,8 +1570,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Group
description: Update/Replace a Group
summary: Update a group
description: Update/Replace a group
tags: [ Groups ]
security:
- BearerAuth: [ ]
@@ -1558,7 +1606,7 @@ paths:
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Group
description: Delete a Group
description: Delete a group
tags: [ Groups ]
security:
- BearerAuth: [ ]
@@ -1584,8 +1632,8 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/rules:
get:
summary: List all Rules
description: Returns a list of all Rules
summary: List all rules
description: Returns a list of all rules
tags: [ Rules ]
security:
- BearerAuth: [ ]
@@ -1608,8 +1656,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Rule
description: Creates a Rule
summary: Create a rule
description: Creates a rule
tags: [ Rules ]
security:
- BearerAuth: [ ]
@@ -1629,8 +1677,8 @@ paths:
$ref: '#/components/schemas/Rule'
/api/rules/{ruleId}:
get:
summary: Retrieve a Rule
description: Get information about a Rules
summary: Retrieve a rule
description: Get information about a rules
tags: [ Rules ]
security:
- BearerAuth: [ ]
@@ -1658,8 +1706,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Rule
description: Update/Replace a Rule
summary: Update a rule
description: Update/Replace a rule
tags: [ Rules ]
security:
- BearerAuth: [ ]
@@ -1693,8 +1741,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Rule
description: Delete a Rule
summary: Delete a rule
description: Delete a rule
tags: [ Rules ]
security:
- BearerAuth: [ ]
@@ -1720,8 +1768,8 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/policies:
get:
summary: List all Policies
description: Returns a list of all Policies
summary: List all policies
description: Returns a list of all policies
tags: [ Policies ]
security:
- BearerAuth: [ ]
@@ -1744,8 +1792,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Policy
description: Creates a Policy
summary: Create a policy
description: Creates a policy
tags: [ Policies ]
security:
- BearerAuth: [ ]
@@ -1755,7 +1803,7 @@ paths:
content:
'application/json':
schema:
$ref: '#/components/schemas/PolicyMinimum'
$ref: '#/components/schemas/PolicyUpdate'
responses:
'200':
description: A Policy Object
@@ -1765,7 +1813,7 @@ paths:
$ref: '#/components/schemas/Policy'
/api/policies/{policyId}:
get:
summary: Retrieve a Policy
summary: Retrieve a policy
description: Get information about a Policies
tags: [ Policies ]
security:
@@ -1794,7 +1842,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Policy
summary: Update a policy
description: Update/Replace a Policy
tags: [ Policies ]
security:
@@ -1812,7 +1860,7 @@ paths:
content:
'application/json':
schema:
$ref: '#/components/schemas/PolicyMinimum'
$ref: '#/components/schemas/PolicyUpdate'
responses:
'200':
description: A Policy object
@@ -1830,7 +1878,7 @@ paths:
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Policy
description: Delete a Policy
description: Delete a policy
tags: [ Policies ]
security:
- BearerAuth: [ ]
@@ -1856,7 +1904,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/routes:
get:
summary: List all Routes
summary: List all routes
description: Returns a list of all routes
tags: [ Routes ]
security:
@@ -1880,7 +1928,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Route
summary: Create a route
description: Creates a Route
tags: [ Routes ]
security:
@@ -1910,7 +1958,7 @@ paths:
/api/routes/{routeId}:
get:
summary: Retrieve a Route
summary: Retrieve a route
description: Get information about a Routes
tags: [ Routes ]
security:
@@ -1939,7 +1987,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Route
summary: Update a route
description: Update/Replace a Route
tags: [ Routes ]
security:
@@ -1975,7 +2023,7 @@ paths:
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Route
description: Delete a Route
description: Delete a route
tags: [ Routes ]
security:
- BearerAuth: [ ]
@@ -2001,7 +2049,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/dns/nameservers:
get:
summary: List all Nameserver Groups
summary: List all nameserver groups
description: Returns a list of all Nameserver Groups
tags: [ DNS ]
security:
@@ -2025,7 +2073,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Nameserver Group
summary: Create a nameserver group
description: Creates a Nameserver Group
tags: [ DNS ]
security:
@@ -2052,9 +2100,10 @@ paths:
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/dns/nameservers/{nsgroupId}:
get:
summary: Retrieve a Nameserver Group
summary: Retrieve a nameserver group
description: Get information about a Nameserver Groups
tags: [ DNS ]
security:
@@ -2083,7 +2132,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Nameserver Group
summary: Update a nameserver group
description: Update/Replace a Nameserver Group
tags: [ DNS ]
security:
@@ -2118,7 +2167,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Nameserver Group
summary: Delete a nameserver group
description: Delete a Nameserver Group
tags: [ DNS ]
security:
@@ -2143,9 +2192,10 @@ paths:
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/dns/settings:
get:
summary: Retrieve DNS Settings
summary: Retrieve DNS settings
description: Returns a DNS settings object
tags: [ DNS ]
security:
@@ -2168,7 +2218,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update DNS Settings
summary: Update DNS settings
description: Updates a DNS settings object
tags: [ DNS ]
security:
@@ -2197,7 +2247,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/events:
get:
summary: List all Events
summary: List all events
description: Returns a list of all events
tags: [ Events ]
security:

View File

@@ -72,6 +72,42 @@ const (
PolicyRuleActionDrop PolicyRuleAction = "drop"
)
// Defines values for PolicyRuleProtocol.
const (
PolicyRuleProtocolAll PolicyRuleProtocol = "all"
PolicyRuleProtocolIcmp PolicyRuleProtocol = "icmp"
PolicyRuleProtocolTcp PolicyRuleProtocol = "tcp"
PolicyRuleProtocolUdp PolicyRuleProtocol = "udp"
)
// Defines values for PolicyRuleMinimumAction.
const (
PolicyRuleMinimumActionAccept PolicyRuleMinimumAction = "accept"
PolicyRuleMinimumActionDrop PolicyRuleMinimumAction = "drop"
)
// Defines values for PolicyRuleMinimumProtocol.
const (
PolicyRuleMinimumProtocolAll PolicyRuleMinimumProtocol = "all"
PolicyRuleMinimumProtocolIcmp PolicyRuleMinimumProtocol = "icmp"
PolicyRuleMinimumProtocolTcp PolicyRuleMinimumProtocol = "tcp"
PolicyRuleMinimumProtocolUdp PolicyRuleMinimumProtocol = "udp"
)
// Defines values for PolicyRuleUpdateAction.
const (
PolicyRuleUpdateActionAccept PolicyRuleUpdateAction = "accept"
PolicyRuleUpdateActionDrop PolicyRuleUpdateAction = "drop"
)
// Defines values for PolicyRuleUpdateProtocol.
const (
PolicyRuleUpdateProtocolAll PolicyRuleUpdateProtocol = "all"
PolicyRuleUpdateProtocolIcmp PolicyRuleUpdateProtocol = "icmp"
PolicyRuleUpdateProtocolTcp PolicyRuleUpdateProtocol = "tcp"
PolicyRuleUpdateProtocolUdp PolicyRuleUpdateProtocol = "udp"
)
// Defines values for UserStatus.
const (
UserStatusActive UserStatus = "active"
@@ -344,7 +380,7 @@ type Policy struct {
Enabled bool `json:"enabled"`
// Id Policy ID
Id string `json:"id"`
Id *string `json:"id,omitempty"`
// Name Policy name identifier
Name string `json:"name"`
@@ -364,6 +400,138 @@ type PolicyMinimum struct {
// Enabled Policy status
Enabled bool `json:"enabled"`
// Id Policy ID
Id *string `json:"id,omitempty"`
// Name Policy name identifier
Name string `json:"name"`
// Query Policy Rego query
Query string `json:"query"`
}
// PolicyRule defines model for PolicyRule.
type PolicyRule struct {
// Action Policy rule accept or drops packets
Action PolicyRuleAction `json:"action"`
// Bidirectional Define if the rule is applicable in both directions, sources, and destinations.
Bidirectional bool `json:"bidirectional"`
// Description Policy rule friendly description
Description *string `json:"description,omitempty"`
// Destinations Policy rule destination groups
Destinations []GroupMinimum `json:"destinations"`
// Enabled Policy rule status
Enabled bool `json:"enabled"`
// Id Policy rule ID
Id *string `json:"id,omitempty"`
// Name Policy rule name identifier
Name string `json:"name"`
// Ports Policy rule affected ports or it ranges list
Ports *[]string `json:"ports,omitempty"`
// Protocol Policy rule type of the traffic
Protocol PolicyRuleProtocol `json:"protocol"`
// Sources Policy rule source groups
Sources []GroupMinimum `json:"sources"`
}
// PolicyRuleAction Policy rule accept or drops packets
type PolicyRuleAction string
// PolicyRuleProtocol Policy rule type of the traffic
type PolicyRuleProtocol string
// PolicyRuleMinimum defines model for PolicyRuleMinimum.
type PolicyRuleMinimum struct {
// Action Policy rule accept or drops packets
Action PolicyRuleMinimumAction `json:"action"`
// Bidirectional Define if the rule is applicable in both directions, sources, and destinations.
Bidirectional bool `json:"bidirectional"`
// Description Policy rule friendly description
Description *string `json:"description,omitempty"`
// Enabled Policy rule status
Enabled bool `json:"enabled"`
// Id Policy rule ID
Id *string `json:"id,omitempty"`
// Name Policy rule name identifier
Name string `json:"name"`
// Ports Policy rule affected ports or it ranges list
Ports *[]string `json:"ports,omitempty"`
// Protocol Policy rule type of the traffic
Protocol PolicyRuleMinimumProtocol `json:"protocol"`
}
// PolicyRuleMinimumAction Policy rule accept or drops packets
type PolicyRuleMinimumAction string
// PolicyRuleMinimumProtocol Policy rule type of the traffic
type PolicyRuleMinimumProtocol string
// PolicyRuleUpdate defines model for PolicyRuleUpdate.
type PolicyRuleUpdate struct {
// Action Policy rule accept or drops packets
Action PolicyRuleUpdateAction `json:"action"`
// Bidirectional Define if the rule is applicable in both directions, sources, and destinations.
Bidirectional bool `json:"bidirectional"`
// Description Policy rule friendly description
Description *string `json:"description,omitempty"`
// Destinations Policy rule destination groups
Destinations []string `json:"destinations"`
// Enabled Policy rule status
Enabled bool `json:"enabled"`
// Id Policy rule ID
Id *string `json:"id,omitempty"`
// Name Policy rule name identifier
Name string `json:"name"`
// Ports Policy rule affected ports or it ranges list
Ports *[]string `json:"ports,omitempty"`
// Protocol Policy rule type of the traffic
Protocol PolicyRuleUpdateProtocol `json:"protocol"`
// Sources Policy rule source groups
Sources []string `json:"sources"`
}
// PolicyRuleUpdateAction Policy rule accept or drops packets
type PolicyRuleUpdateAction string
// PolicyRuleUpdateProtocol Policy rule type of the traffic
type PolicyRuleUpdateProtocol string
// PolicyUpdate defines model for PolicyUpdate.
type PolicyUpdate struct {
// Description Policy friendly description
Description string `json:"description"`
// Enabled Policy status
Enabled bool `json:"enabled"`
// Id Policy ID
Id *string `json:"id,omitempty"`
// Name Policy name identifier
Name string `json:"name"`
@@ -371,36 +539,9 @@ type PolicyMinimum struct {
Query string `json:"query"`
// Rules Policy rule object for policy UI editor
Rules []PolicyRule `json:"rules"`
Rules []PolicyRuleUpdate `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
@@ -680,10 +821,10 @@ type PutApiGroupsGroupIdJSONRequestBody = GroupRequest
type PutApiPeersPeerIdJSONRequestBody = PeerRequest
// PostApiPoliciesJSONRequestBody defines body for PostApiPolicies for application/json ContentType.
type PostApiPoliciesJSONRequestBody = PolicyMinimum
type PostApiPoliciesJSONRequestBody = PolicyUpdate
// PutApiPoliciesPolicyIdJSONRequestBody defines body for PutApiPoliciesPolicyId for application/json ContentType.
type PutApiPoliciesPolicyIdJSONRequestBody = PolicyMinimum
type PutApiPoliciesPolicyIdJSONRequestBody = PolicyUpdate
// PostApiRoutesJSONRequestBody defines body for PostApiRoutes for application/json ContentType.
type PostApiRoutesJSONRequestBody = RouteRequest

View File

@@ -6,7 +6,6 @@ import (
"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"
@@ -47,7 +46,17 @@ func (h *Policies) GetAllPolicies(w http.ResponseWriter, r *http.Request) {
return
}
util.WriteJSONObject(w, accountPolicies)
policies := []*api.Policy{}
for _, policy := range accountPolicies {
resp := toPolicyResponse(account, policy)
if len(resp.Rules) == 0 {
util.WriteError(status.Errorf(status.Internal, "no rules in the policy"), w)
return
}
policies = append(policies, resp)
}
util.WriteJSONObject(w, policies)
}
// UpdatePolicy handles update to a policy identified by a given ID
@@ -78,63 +87,7 @@ func (h *Policies) UpdatePolicy(w http.ResponseWriter, r *http.Request) {
return
}
var req api.PutApiPoliciesPolicyIdJSONRequestBody
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))
h.savePolicy(w, r, account, user, policyID)
}
// CreatePolicy handles policy creation request
@@ -146,9 +99,19 @@ func (h *Policies) CreatePolicy(w http.ResponseWriter, r *http.Request) {
return
}
var req api.PostApiPoliciesJSONRequestBody
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
h.savePolicy(w, r, account, user, "")
}
// savePolicy handles policy creation and update
func (h *Policies) savePolicy(
w http.ResponseWriter,
r *http.Request,
account *server.Account,
user *server.User,
policyID string,
) {
var req api.PutApiPoliciesPolicyIdJSONRequestBody
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
return
}
@@ -158,49 +121,97 @@ func (h *Policies) CreatePolicy(w http.ResponseWriter, r *http.Request) {
return
}
policy := &server.Policy{
ID: xid.New().String(),
if len(req.Rules) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "policy rules shouldn't be empty"), w)
return
}
if policyID == "" {
policyID = xid.New().String()
}
policy := server.Policy{
ID: policyID,
Name: req.Name,
Enabled: req.Enabled,
Description: req.Description,
Query: req.Query,
}
for _, r := range req.Rules {
pr := server.PolicyRule{
ID: policyID, //TODO: when policy can contain multiple rules, need refactor
Name: r.Name,
Destinations: groupMinimumsToStrings(account, r.Destinations),
Sources: groupMinimumsToStrings(account, r.Sources),
Bidirectional: r.Bidirectional,
}
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)
pr.Enabled = r.Enabled
if r.Description != nil {
pr.Description = *r.Description
}
switch r.Action {
case api.PolicyRuleUpdateActionAccept:
pr.Action = server.PolicyTrafficActionAccept
case api.PolicyRuleUpdateActionDrop:
pr.Action = server.PolicyTrafficActionDrop
default:
util.WriteError(status.Errorf(status.InvalidArgument, "unknown action type"), w)
return
}
switch r.Protocol {
case api.PolicyRuleUpdateProtocolAll:
pr.Protocol = server.PolicyRuleProtocolALL
case api.PolicyRuleUpdateProtocolTcp:
pr.Protocol = server.PolicyRuleProtocolTCP
case api.PolicyRuleUpdateProtocolUdp:
pr.Protocol = server.PolicyRuleProtocolUDP
case api.PolicyRuleUpdateProtocolIcmp:
pr.Protocol = server.PolicyRuleProtocolICMP
default:
util.WriteError(status.Errorf(status.InvalidArgument, "unknown protocol type: %v", r.Protocol), w)
return
}
if r.Ports != nil && len(*r.Ports) != 0 {
ports := *r.Ports
pr.Ports = ports[:]
}
// validate policy object
switch pr.Protocol {
case server.PolicyRuleProtocolALL, server.PolicyRuleProtocolICMP:
if len(pr.Ports) != 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "for ALL or ICMP protocol ports is not allowed"), w)
return
}
if !pr.Bidirectional {
util.WriteError(status.Errorf(status.InvalidArgument, "for ALL or ICMP protocol type flow can be only bi-directional"), w)
return
}
case server.PolicyRuleProtocolTCP, server.PolicyRuleProtocolUDP:
if !pr.Bidirectional && len(pr.Ports) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "for ALL or ICMP protocol type flow can be only bi-directional"), w)
return
}
policy.Rules = append(policy.Rules, &pr)
}
policy.Rules = append(policy.Rules, &pr)
}
if err := policy.UpdateQueryFromRules(); err != nil {
if err := h.accountManager.SavePolicy(account.Id, user.Id, &policy); err != nil {
util.WriteError(err, w)
return
}
if err = h.accountManager.SavePolicy(account.Id, user.Id, policy); err != nil {
util.WriteError(err, w)
resp := toPolicyResponse(account, &policy)
if len(resp.Rules) == 0 {
util.WriteError(status.Errorf(status.Internal, "no rules in the policy"), w)
return
}
util.WriteJSONObject(w, toPolicyResponse(account, policy))
util.WriteJSONObject(w, resp)
}
// DeletePolicy handles policy deletion request
@@ -252,7 +263,13 @@ func (h *Policies) GetPolicy(w http.ResponseWriter, r *http.Request) {
return
}
util.WriteJSONObject(w, toPolicyResponse(account, policy))
resp := toPolicyResponse(account, policy)
if len(resp.Rules) == 0 {
util.WriteError(status.Errorf(status.Internal, "no rules in the policy"), w)
return
}
util.WriteJSONObject(w, resp)
default:
util.WriteError(status.Errorf(status.NotFound, "method not found"), w)
}
@@ -261,22 +278,24 @@ func (h *Policies) GetPolicy(w http.ResponseWriter, r *http.Request) {
func toPolicyResponse(account *server.Account, policy *server.Policy) *api.Policy {
cache := make(map[string]api.GroupMinimum)
ap := &api.Policy{
Id: policy.ID,
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,
Id: &r.ID,
Name: r.Name,
Enabled: r.Enabled,
Description: &r.Description,
Bidirectional: r.Bidirectional,
Protocol: api.PolicyRuleProtocol(r.Protocol),
Action: api.PolicyRuleAction(r.Action),
}
if len(r.Ports) != 0 {
portsCopy := r.Ports[:]
rule.Ports = &portsCopy
}
for _, gid := range r.Sources {
_, ok := cache[gid]
@@ -314,13 +333,13 @@ func toPolicyResponse(account *server.Account, policy *server.Policy) *api.Polic
return ap
}
func groupMinimumsToStrings(account *server.Account, gm []api.GroupMinimum) []string {
func groupMinimumsToStrings(account *server.Account, gm []string) []string {
result := make([]string, 0, len(gm))
for _, gm := range gm {
if _, ok := account.Groups[gm.Id]; ok {
for _, g := range gm {
if _, ok := account.Groups[g]; !ok {
continue
}
result = append(result, gm.Id)
result = append(result, g)
}
return result
}

View File

@@ -0,0 +1,315 @@
package http
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/status"
"github.com/gorilla/mux"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/magiconair/properties/assert"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/mock_server"
)
func initPoliciesTestData(policies ...*server.Policy) *Policies {
testPolicies := make(map[string]*server.Policy, len(policies))
for _, policy := range policies {
testPolicies[policy.ID] = policy
}
return &Policies{
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"
policy.Rules[0].ID = "id-was-set"
}
return nil
},
SaveRuleFunc: func(_, _ string, rule *server.Rule) error {
if !strings.HasPrefix(rule.ID, "id-") {
rule.ID = "id-was-set"
}
return nil
},
GetRuleFunc: func(_, ruleID, _ string) (*server.Rule, error) {
if ruleID != "idoftherule" {
return nil, fmt.Errorf("not found")
}
return &server.Rule{
ID: "idoftherule",
Name: "Rule",
Source: []string{"idofsrcrule"},
Destination: []string{"idofdestrule"},
Flow: server.TrafficFlowBidirect,
}, nil
},
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
user := server.NewAdminUser("test_user")
return &server.Account{
Id: claims.AccountId,
Domain: "hotmail.com",
Policies: []*server.Policy{
{ID: "id-existed"},
},
Groups: map[string]*server.Group{
"F": {ID: "F"},
"G": {ID: "G"},
},
Users: map[string]*server.User{
"test_user": user,
},
}, user, nil
},
},
claimsExtractor: jwtclaims.NewClaimsExtractor(
jwtclaims.WithFromRequestContext(func(r *http.Request) jwtclaims.AuthorizationClaims {
return jwtclaims.AuthorizationClaims{
UserId: "test_user",
Domain: "hotmail.com",
AccountId: "test_id",
}
}),
),
}
}
func TestPoliciesGetPolicy(t *testing.T) {
tt := []struct {
name string
expectedStatus int
expectedBody bool
requestType string
requestPath string
requestBody io.Reader
}{
{
name: "GetPolicy OK",
expectedBody: true,
requestType: http.MethodGet,
requestPath: "/api/policies/idofthepolicy",
expectedStatus: http.StatusOK,
},
{
name: "GetPolicy not found",
requestType: http.MethodGet,
requestPath: "/api/policies/notexists",
expectedStatus: http.StatusNotFound,
},
}
policy := &server.Policy{
ID: "idofthepolicy",
Name: "Rule",
Rules: []*server.PolicyRule{
{ID: "idoftherule", Name: "Rule"},
},
}
p := initPoliciesTestData(policy)
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
recorder := httptest.NewRecorder()
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
router := mux.NewRouter()
router.HandleFunc("/api/policies/{policyId}", p.GetPolicy).Methods("GET")
router.ServeHTTP(recorder, req)
res := recorder.Result()
defer res.Body.Close()
if status := recorder.Code; status != tc.expectedStatus {
t.Errorf("handler returned wrong status code: got %v want %v",
status, tc.expectedStatus)
return
}
if !tc.expectedBody {
return
}
content, err := io.ReadAll(res.Body)
if err != nil {
t.Fatalf("I don't know what I expected; %v", err)
}
var got api.Policy
if err = json.Unmarshal(content, &got); err != nil {
t.Fatalf("Sent content is not in correct json format; %v", err)
}
assert.Equal(t, *got.Id, policy.ID)
assert.Equal(t, got.Name, policy.Name)
})
}
}
func TestPoliciesWritePolicy(t *testing.T) {
str := func(s string) *string { return &s }
tt := []struct {
name string
expectedStatus int
expectedBody bool
expectedPolicy *api.Policy
requestType string
requestPath string
requestBody io.Reader
}{
{
name: "WritePolicy POST OK",
requestType: http.MethodPost,
requestPath: "/api/policies",
requestBody: bytes.NewBuffer(
[]byte(`{
"Name":"Default POSTed Policy",
"Rules":[
{
"Name":"Default POSTed Policy",
"Description": "Description",
"Protocol": "tcp",
"Action": "accept",
"Bidirectional":true
}
]}`)),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedPolicy: &api.Policy{
Id: str("id-was-set"),
Name: "Default POSTed Policy",
Rules: []api.PolicyRule{
{
Id: str("id-was-set"),
Name: "Default POSTed Policy",
Description: str("Description"),
Protocol: "tcp",
Action: "accept",
Bidirectional: true,
},
},
},
},
{
name: "WritePolicy POST Invalid Name",
requestType: http.MethodPost,
requestPath: "/api/policies",
requestBody: bytes.NewBuffer(
[]byte(`{"Name":""}`)),
expectedStatus: http.StatusUnprocessableEntity,
expectedBody: false,
},
{
name: "WritePolicy PUT OK",
requestType: http.MethodPut,
requestPath: "/api/policies/id-existed",
requestBody: bytes.NewBuffer(
[]byte(`{
"ID": "id-existed",
"Name":"Default POSTed Policy",
"Rules":[
{
"ID": "id-existed",
"Name":"Default POSTed Policy",
"Description": "Description",
"Protocol": "tcp",
"Action": "accept",
"Bidirectional":true
}
]}`)),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedPolicy: &api.Policy{
Id: str("id-existed"),
Name: "Default POSTed Policy",
Rules: []api.PolicyRule{
{
Id: str("id-existed"),
Name: "Default POSTed Policy",
Description: str("Description"),
Protocol: "tcp",
Action: "accept",
Bidirectional: true,
},
},
},
},
{
name: "WritePolicy PUT Invalid Name",
requestType: http.MethodPut,
requestPath: "/api/policies/id-existed",
requestBody: bytes.NewBuffer(
[]byte(`{"ID":"id-existed","Name":"","Rules":[{"ID":"id-existed"}]}`)),
expectedStatus: http.StatusUnprocessableEntity,
},
}
p := initPoliciesTestData(&server.Policy{
ID: "id-existed",
Name: "Default POSTed Rule",
Rules: []*server.PolicyRule{
{
ID: "id-existed",
Name: "Default POSTed Rule",
Bidirectional: true,
},
},
})
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
recorder := httptest.NewRecorder()
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
router := mux.NewRouter()
router.HandleFunc("/api/policies", p.CreatePolicy).Methods("POST")
router.HandleFunc("/api/policies/{policyId}", p.UpdatePolicy).Methods("PUT")
router.ServeHTTP(recorder, req)
res := recorder.Result()
defer res.Body.Close()
content, err := io.ReadAll(res.Body)
if err != nil {
t.Fatalf("I don't know what I expected; %v", err)
return
}
if status := recorder.Code; status != tc.expectedStatus {
t.Errorf("handler returned wrong status code: got %v want %v, content: %s",
status, tc.expectedStatus, string(content))
return
}
if !tc.expectedBody {
return
}
expected, err := json.Marshal(tc.expectedPolicy)
if err != nil {
t.Fatalf("marshal expected policy: %v", err)
return
}
assert.Equal(t, strings.Trim(string(content), " \n"), string(expected), "content mismatch")
})
}
}

View File

@@ -112,10 +112,6 @@ func (h *RulesHandler) UpdateRule(w http.ResponseWriter, r *http.Request) {
policy.Rules[0].Destinations = reqDestinations
policy.Rules[0].Enabled = !req.Disabled
policy.Rules[0].Description = req.Description
if err := policy.UpdateQueryFromRules(); err != nil {
util.WriteError(err, w)
return
}
switch req.Flow {
case server.TrafficFlowBidirectString:

View File

@@ -30,9 +30,6 @@ func initRulesTestData(rules ...*server.Rule) *RulesHandler {
if err != nil {
panic(err)
}
if err := policy.UpdateQueryFromRules(); err != nil {
panic(err)
}
testPolicies[policy.ID] = policy
}
return &RulesHandler{