mirror of
https://github.com/netbirdio/netbird.git
synced 2025-02-22 21:21:23 +01:00
Remove deprecated Rules API endpoints (#1523)
This commit is contained in:
parent
042141db06
commit
0b3b50c705
@ -227,9 +227,6 @@ type Account struct {
|
||||
PostureChecks []*posture.Checks `gorm:"foreignKey:AccountID;references:id"`
|
||||
// Settings is a dictionary of Account settings
|
||||
Settings *Settings `gorm:"embedded;embeddedPrefix:settings_"`
|
||||
// deprecated on store and api level
|
||||
Rules map[string]*Rule `json:"-" gorm:"-"`
|
||||
RulesG []Rule `json:"-" gorm:"-"`
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
|
@ -258,18 +258,6 @@ func TestStore(t *testing.T) {
|
||||
t.Errorf("failed to restore a FileStore file - missing Group all")
|
||||
}
|
||||
|
||||
if restoredAccount.Rules["all"] == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Rule all")
|
||||
return
|
||||
}
|
||||
|
||||
if restoredAccount.Rules["dmz"] == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Rule dmz")
|
||||
return
|
||||
}
|
||||
assert.Equal(t, account.Rules["all"], restoredAccount.Rules["all"], "failed to restore a FileStore file - missing Rule all")
|
||||
assert.Equal(t, account.Rules["dmz"], restoredAccount.Rules["dmz"], "failed to restore a FileStore file - missing Rule dmz")
|
||||
|
||||
if len(restoredAccount.Policies) != 2 {
|
||||
t.Errorf("failed to restore a FileStore file - missing Policies")
|
||||
return
|
||||
@ -411,7 +399,6 @@ func TestFileStore_GetAccount(t *testing.T) {
|
||||
assert.Len(t, account.Peers, len(expected.Peers))
|
||||
assert.Len(t, account.Users, len(expected.Users))
|
||||
assert.Len(t, account.SetupKeys, len(expected.SetupKeys))
|
||||
assert.Len(t, account.Rules, len(expected.Rules))
|
||||
assert.Len(t, account.Routes, len(expected.Routes))
|
||||
assert.Len(t, account.NameServerGroups, len(expected.NameServerGroups))
|
||||
}
|
||||
|
@ -17,8 +17,6 @@ tags:
|
||||
description: Interact with and view information about setup keys.
|
||||
- name: Groups
|
||||
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: Posture Checks
|
||||
@ -621,73 +619,6 @@ components:
|
||||
$ref: '#/components/schemas/PeerMinimum'
|
||||
required:
|
||||
- peers
|
||||
RuleMinimum:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: Rule name identifier
|
||||
type: string
|
||||
example: Default
|
||||
description:
|
||||
description: Rule friendly description
|
||||
type: string
|
||||
example: This is a default rule that allows connections between all the resources
|
||||
disabled:
|
||||
description: Rules status
|
||||
type: boolean
|
||||
example: false
|
||||
flow:
|
||||
description: Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
type: string
|
||||
example: bidirect
|
||||
required:
|
||||
- name
|
||||
- description
|
||||
- disabled
|
||||
- flow
|
||||
RuleRequest:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuleMinimum'
|
||||
- type: object
|
||||
properties:
|
||||
sources:
|
||||
type: array
|
||||
description: List of source group IDs
|
||||
items:
|
||||
type: string
|
||||
example: "ch8i4ug6lnn4g9hqv7m1"
|
||||
destinations:
|
||||
type: array
|
||||
description: List of destination group IDs
|
||||
items:
|
||||
type: string
|
||||
example: "ch8i4ug6lnn4g9hqv7m0"
|
||||
Rule:
|
||||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
id:
|
||||
description: Rule ID
|
||||
type: string
|
||||
example: ch8i4ug6lnn4g9hqv7mg
|
||||
required:
|
||||
- id
|
||||
- $ref: '#/components/schemas/RuleMinimum'
|
||||
- type: object
|
||||
properties:
|
||||
sources:
|
||||
description: Rule source group IDs
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/GroupMinimum'
|
||||
destinations:
|
||||
description: Rule destination group IDs
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/GroupMinimum'
|
||||
required:
|
||||
- sources
|
||||
- destinations
|
||||
PolicyRuleMinimum:
|
||||
type: object
|
||||
properties:
|
||||
@ -2035,147 +1966,6 @@ paths:
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/rules:
|
||||
get:
|
||||
summary: List all Rules
|
||||
description: Returns a list of all rules. This will be deprecated in favour of `/api/policies`.
|
||||
tags: [ Rules ]
|
||||
deprecated: true
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON Array of Rules
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Rule'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
summary: Create a Rule
|
||||
description: Creates a rule. This will be deprecated in favour of `/api/policies`.
|
||||
deprecated: true
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
requestBody:
|
||||
description: New Rule request
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: '#/components/schemas/RuleRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: A Rule Object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rule'
|
||||
/api/rules/{ruleId}:
|
||||
get:
|
||||
summary: Retrieve a Rule
|
||||
description: Get information about a rules. This will be deprecated in favour of `/api/policies/{policyID}`.
|
||||
deprecated: true
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: ruleId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of a rule
|
||||
responses:
|
||||
'200':
|
||||
description: A Rule object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rule'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update a Rule
|
||||
description: Update/Replace a rule. This will be deprecated in favour of `/api/policies/{policyID}`.
|
||||
deprecated: true
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: ruleId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of a rule
|
||||
requestBody:
|
||||
description: Update Rule request
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: '#/components/schemas/RuleRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: A Rule object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rule'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a Rule
|
||||
description: Delete a rule. This will be deprecated in favour of `/api/policies/{policyID}`.
|
||||
deprecated: true
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: ruleId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of a rule
|
||||
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: List all Policies
|
||||
|
@ -976,66 +976,6 @@ type RouteRequest struct {
|
||||
PeerGroups *[]string `json:"peer_groups,omitempty"`
|
||||
}
|
||||
|
||||
// Rule defines model for Rule.
|
||||
type Rule struct {
|
||||
// Description Rule friendly description
|
||||
Description string `json:"description"`
|
||||
|
||||
// Destinations Rule destination group IDs
|
||||
Destinations []GroupMinimum `json:"destinations"`
|
||||
|
||||
// Disabled Rules status
|
||||
Disabled bool `json:"disabled"`
|
||||
|
||||
// Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
Flow string `json:"flow"`
|
||||
|
||||
// Id Rule ID
|
||||
Id string `json:"id"`
|
||||
|
||||
// Name Rule name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// Sources Rule source group IDs
|
||||
Sources []GroupMinimum `json:"sources"`
|
||||
}
|
||||
|
||||
// RuleMinimum defines model for RuleMinimum.
|
||||
type RuleMinimum struct {
|
||||
// Description Rule friendly description
|
||||
Description string `json:"description"`
|
||||
|
||||
// Disabled Rules status
|
||||
Disabled bool `json:"disabled"`
|
||||
|
||||
// Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
Flow string `json:"flow"`
|
||||
|
||||
// Name Rule name identifier
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// RuleRequest defines model for RuleRequest.
|
||||
type RuleRequest struct {
|
||||
// Description Rule friendly description
|
||||
Description string `json:"description"`
|
||||
|
||||
// Destinations List of destination group IDs
|
||||
Destinations *[]string `json:"destinations,omitempty"`
|
||||
|
||||
// Disabled Rules status
|
||||
Disabled bool `json:"disabled"`
|
||||
|
||||
// Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
Flow string `json:"flow"`
|
||||
|
||||
// Name Rule name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// Sources List of source group IDs
|
||||
Sources *[]string `json:"sources,omitempty"`
|
||||
}
|
||||
|
||||
// SetupKey defines model for SetupKey.
|
||||
type SetupKey struct {
|
||||
// AutoGroups List of group IDs to auto-assign to peers registered with this key
|
||||
@ -1219,12 +1159,6 @@ type PostApiRoutesJSONRequestBody = RouteRequest
|
||||
// PutApiRoutesRouteIdJSONRequestBody defines body for PutApiRoutesRouteId for application/json ContentType.
|
||||
type PutApiRoutesRouteIdJSONRequestBody = RouteRequest
|
||||
|
||||
// PostApiRulesJSONRequestBody defines body for PostApiRules for application/json ContentType.
|
||||
type PostApiRulesJSONRequestBody = RuleRequest
|
||||
|
||||
// PutApiRulesRuleIdJSONRequestBody defines body for PutApiRulesRuleId for application/json ContentType.
|
||||
type PutApiRulesRuleIdJSONRequestBody = RuleRequest
|
||||
|
||||
// PostApiSetupKeysJSONRequestBody defines body for PostApiSetupKeys for application/json ContentType.
|
||||
type PostApiSetupKeysJSONRequestBody = SetupKeyRequest
|
||||
|
||||
|
@ -85,7 +85,6 @@ func APIHandler(ctx context.Context, accountManager s.AccountManager, LocationMa
|
||||
api.addUsersEndpoint()
|
||||
api.addUsersTokensEndpoint()
|
||||
api.addSetupKeysEndpoint()
|
||||
api.addRulesEndpoint()
|
||||
api.addPoliciesEndpoint()
|
||||
api.addGroupsEndpoint()
|
||||
api.addRoutesEndpoint()
|
||||
@ -158,15 +157,6 @@ func (apiHandler *apiHandler) addSetupKeysEndpoint() {
|
||||
apiHandler.Router.HandleFunc("/setup-keys/{keyId}", keysHandler.UpdateSetupKey).Methods("PUT", "OPTIONS")
|
||||
}
|
||||
|
||||
func (apiHandler *apiHandler) addRulesEndpoint() {
|
||||
rulesHandler := NewRulesHandler(apiHandler.AccountManager, apiHandler.AuthCfg)
|
||||
apiHandler.Router.HandleFunc("/rules", rulesHandler.GetAllRules).Methods("GET", "OPTIONS")
|
||||
apiHandler.Router.HandleFunc("/rules", rulesHandler.CreateRule).Methods("POST", "OPTIONS")
|
||||
apiHandler.Router.HandleFunc("/rules/{ruleId}", rulesHandler.UpdateRule).Methods("PUT", "OPTIONS")
|
||||
apiHandler.Router.HandleFunc("/rules/{ruleId}", rulesHandler.GetRule).Methods("GET", "OPTIONS")
|
||||
apiHandler.Router.HandleFunc("/rules/{ruleId}", rulesHandler.DeleteRule).Methods("DELETE", "OPTIONS")
|
||||
}
|
||||
|
||||
func (apiHandler *apiHandler) addPoliciesEndpoint() {
|
||||
policiesHandler := NewPoliciesHandler(apiHandler.AccountManager, apiHandler.AuthCfg)
|
||||
apiHandler.Router.HandleFunc("/policies", policiesHandler.GetAllPolicies).Methods("GET", "OPTIONS")
|
||||
|
@ -3,7 +3,6 @@ package http
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -44,24 +43,6 @@ func initPoliciesTestData(policies ...*server.Policy) *Policies {
|
||||
}
|
||||
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{
|
||||
|
@ -1,305 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/xid"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// RulesHandler is a handler that returns rules of the account
|
||||
type RulesHandler struct {
|
||||
accountManager server.AccountManager
|
||||
claimsExtractor *jwtclaims.ClaimsExtractor
|
||||
}
|
||||
|
||||
// NewRulesHandler creates a new RulesHandler HTTP handler
|
||||
func NewRulesHandler(accountManager server.AccountManager, authCfg AuthCfg) *RulesHandler {
|
||||
return &RulesHandler{
|
||||
accountManager: accountManager,
|
||||
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
||||
jwtclaims.WithAudience(authCfg.Audience),
|
||||
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllRules list for the account
|
||||
func (h *RulesHandler) GetAllRules(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
|
||||
}
|
||||
rules := []*api.Rule{}
|
||||
for _, policy := range accountPolicies {
|
||||
for _, r := range policy.Rules {
|
||||
rules = append(rules, toRuleResponse(account, r.ToRule()))
|
||||
}
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, rules)
|
||||
}
|
||||
|
||||
// UpdateRule handles update to a rule identified by a given ID
|
||||
func (h *RulesHandler) UpdateRule(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)
|
||||
ruleID := vars["ruleId"]
|
||||
if len(ruleID) == 0 {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid rule ID"), w)
|
||||
return
|
||||
}
|
||||
|
||||
policy, err := h.accountManager.GetPolicy(account.Id, ruleID, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
var req api.PutApiRulesRuleIdJSONRequestBody
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "rule name shouldn't be empty"), w)
|
||||
return
|
||||
}
|
||||
|
||||
var reqSources []string
|
||||
if req.Sources != nil {
|
||||
reqSources = *req.Sources
|
||||
}
|
||||
|
||||
var reqDestinations []string
|
||||
if req.Destinations != nil {
|
||||
reqDestinations = *req.Destinations
|
||||
}
|
||||
|
||||
if len(policy.Rules) != 1 {
|
||||
util.WriteError(status.Errorf(status.Internal, "policy should contain exactly one rule"), w)
|
||||
return
|
||||
}
|
||||
|
||||
policy.Name = req.Name
|
||||
policy.Description = req.Description
|
||||
policy.Enabled = !req.Disabled
|
||||
policy.Rules[0].ID = ruleID
|
||||
policy.Rules[0].Name = req.Name
|
||||
policy.Rules[0].Sources = reqSources
|
||||
policy.Rules[0].Destinations = reqDestinations
|
||||
policy.Rules[0].Enabled = !req.Disabled
|
||||
policy.Rules[0].Description = req.Description
|
||||
|
||||
switch req.Flow {
|
||||
case server.TrafficFlowBidirectString:
|
||||
policy.Rules[0].Action = server.PolicyTrafficActionAccept
|
||||
default:
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "unknown flow type"), w)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.accountManager.SavePolicy(account.Id, user.Id, policy)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
resp := toRuleResponse(account, policy.Rules[0].ToRule())
|
||||
|
||||
util.WriteJSONObject(w, &resp)
|
||||
}
|
||||
|
||||
// CreateRule handles rule creation request
|
||||
func (h *RulesHandler) CreateRule(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.PostApiRulesJSONRequestBody
|
||||
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, "rule name shouldn't be empty"), w)
|
||||
return
|
||||
}
|
||||
|
||||
var reqSources []string
|
||||
if req.Sources != nil {
|
||||
reqSources = *req.Sources
|
||||
}
|
||||
|
||||
var reqDestinations []string
|
||||
if req.Destinations != nil {
|
||||
reqDestinations = *req.Destinations
|
||||
}
|
||||
|
||||
rule := server.Rule{
|
||||
ID: xid.New().String(),
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
resp := toRuleResponse(account, &rule)
|
||||
|
||||
util.WriteJSONObject(w, &resp)
|
||||
}
|
||||
|
||||
// DeleteRule handles rule deletion request
|
||||
func (h *RulesHandler) DeleteRule(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
|
||||
|
||||
rID := mux.Vars(r)["ruleId"]
|
||||
if len(rID) == 0 {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid rule ID"), w)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.accountManager.DeletePolicy(aID, rID, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, emptyObject{})
|
||||
}
|
||||
|
||||
// GetRule handles a group Get request identified by ID
|
||||
func (h *RulesHandler) GetRule(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:
|
||||
ruleID := mux.Vars(r)["ruleId"]
|
||||
if len(ruleID) == 0 {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid rule ID"), w)
|
||||
return
|
||||
}
|
||||
|
||||
policy, err := h.accountManager.GetPolicy(account.Id, ruleID, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, toRuleResponse(account, policy.Rules[0].ToRule()))
|
||||
default:
|
||||
util.WriteError(status.Errorf(status.NotFound, "method not found"), w)
|
||||
}
|
||||
}
|
||||
|
||||
func toRuleResponse(account *server.Account, rule *server.Rule) *api.Rule {
|
||||
cache := make(map[string]api.GroupMinimum)
|
||||
gr := api.Rule{
|
||||
Id: rule.ID,
|
||||
Name: rule.Name,
|
||||
Description: rule.Description,
|
||||
Disabled: rule.Disabled,
|
||||
}
|
||||
|
||||
switch rule.Flow {
|
||||
case server.TrafficFlowBidirect:
|
||||
gr.Flow = server.TrafficFlowBidirectString
|
||||
default:
|
||||
gr.Flow = "unknown"
|
||||
}
|
||||
|
||||
for _, gid := range rule.Source {
|
||||
_, 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),
|
||||
}
|
||||
|
||||
gr.Sources = append(gr.Sources, minimum)
|
||||
cache[gid] = minimum
|
||||
}
|
||||
}
|
||||
|
||||
for _, gid := range rule.Destination {
|
||||
cachedMinimum, ok := cache[gid]
|
||||
if ok {
|
||||
gr.Destinations = append(gr.Destinations, cachedMinimum)
|
||||
continue
|
||||
}
|
||||
if group, ok := account.Groups[gid]; ok {
|
||||
minimum := api.GroupMinimum{
|
||||
Id: group.ID,
|
||||
Name: group.Name,
|
||||
PeersCount: len(group.Peers),
|
||||
}
|
||||
gr.Destinations = append(gr.Destinations, minimum)
|
||||
cache[gid] = minimum
|
||||
}
|
||||
}
|
||||
|
||||
return &gr
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
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 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)
|
||||
}
|
||||
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"
|
||||
}
|
||||
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",
|
||||
Rules: map[string]*server.Rule{"id-existed": {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 TestRulesGetRule(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
expectedStatus int
|
||||
expectedBody bool
|
||||
requestType string
|
||||
requestPath string
|
||||
requestBody io.Reader
|
||||
}{
|
||||
{
|
||||
name: "GetRule OK",
|
||||
expectedBody: true,
|
||||
requestType: http.MethodGet,
|
||||
requestPath: "/api/rules/idoftherule",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "GetRule not found",
|
||||
requestType: http.MethodGet,
|
||||
requestPath: "/api/rules/notexists",
|
||||
expectedStatus: http.StatusNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
rule := &server.Rule{
|
||||
ID: "idoftherule",
|
||||
Name: "Rule",
|
||||
}
|
||||
|
||||
p := initRulesTestData(rule)
|
||||
|
||||
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/rules/{ruleId}", p.GetRule).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.Rule
|
||||
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, rule.ID)
|
||||
assert.Equal(t, got.Name, rule.Name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRulesWriteRule(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
expectedStatus int
|
||||
expectedBody bool
|
||||
expectedRule *api.Rule
|
||||
requestType string
|
||||
requestPath string
|
||||
requestBody io.Reader
|
||||
}{
|
||||
{
|
||||
name: "WriteRule POST OK",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/rules",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"Name":"Default POSTed Rule","Flow":"bidirect"}`)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: true,
|
||||
expectedRule: &api.Rule{
|
||||
Id: "id-was-set",
|
||||
Name: "Default POSTed Rule",
|
||||
Flow: server.TrafficFlowBidirectString,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WriteRule POST Invalid Name",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/rules",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"Name":"","Flow":"bidirect"}`)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "WriteRule PUT OK",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/rules/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"Name":"Default POSTed Rule","Flow":"bidirect"}`)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: true,
|
||||
expectedRule: &api.Rule{
|
||||
Id: "id-existed",
|
||||
Name: "Default POSTed Rule",
|
||||
Flow: server.TrafficFlowBidirectString,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WriteRule PUT Invalid Name",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/rules/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"Name":"","Flow":"bidirect"}`)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
},
|
||||
}
|
||||
|
||||
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) {
|
||||
recorder := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/rules", p.CreateRule).Methods("POST")
|
||||
router.HandleFunc("/api/rules/{ruleId}", p.UpdateRule).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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
got := &api.Rule{}
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
@ -38,10 +38,7 @@ type MockAccountManager struct {
|
||||
ListGroupsFunc func(accountID string) ([]*server.Group, error)
|
||||
GroupAddPeerFunc func(accountID, groupID, peerID string) error
|
||||
GroupDeletePeerFunc func(accountID, groupID, peerID string) error
|
||||
GetRuleFunc func(accountID, ruleID, userID string) (*server.Rule, error)
|
||||
SaveRuleFunc func(accountID, userID string, rule *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
|
||||
@ -302,22 +299,6 @@ func (am *MockAccountManager) GroupDeletePeer(accountID, groupID, peerID string)
|
||||
return status.Errorf(codes.Unimplemented, "method GroupDeletePeer is not implemented")
|
||||
}
|
||||
|
||||
// GetRule mock implementation of GetRule from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetRule(accountID, ruleID, userID string) (*server.Rule, error) {
|
||||
if am.GetRuleFunc != nil {
|
||||
return am.GetRuleFunc(accountID, ruleID, userID)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetRule is not implemented")
|
||||
}
|
||||
|
||||
// SaveRule mock implementation of SaveRule from server.AccountManager interface
|
||||
func (am *MockAccountManager) SaveRule(accountID, userID string, rule *server.Rule) error {
|
||||
if am.SaveRuleFunc != nil {
|
||||
return am.SaveRuleFunc(accountID, userID, rule)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method SaveRule is not implemented")
|
||||
}
|
||||
|
||||
// DeleteRule mock implementation of DeleteRule from server.AccountManager interface
|
||||
func (am *MockAccountManager) DeleteRule(accountID, ruleID, userID string) error {
|
||||
if am.DeleteRuleFunc != nil {
|
||||
@ -326,14 +307,6 @@ func (am *MockAccountManager) DeleteRule(accountID, ruleID, userID string) error
|
||||
return status.Errorf(codes.Unimplemented, "method DeleteRule is not implemented")
|
||||
}
|
||||
|
||||
// ListRules mock implementation of ListRules from server.AccountManager interface
|
||||
func (am *MockAccountManager) ListRules(accountID, userID string) ([]*server.Rule, error) {
|
||||
if am.ListRulesFunc != nil {
|
||||
return am.ListRulesFunc(accountID, userID)
|
||||
}
|
||||
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 {
|
||||
|
@ -52,6 +52,17 @@ const (
|
||||
PolicyRuleFlowBidirect = PolicyRuleDirection("bidirect")
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultRuleName is a name for the Default rule that is created for every account
|
||||
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"
|
||||
)
|
||||
|
||||
const (
|
||||
firewallRuleDirectionIN = 0
|
||||
firewallRuleDirectionOUT = 1
|
||||
@ -119,19 +130,6 @@ func (pm *PolicyRule) Copy() *PolicyRule {
|
||||
return rule
|
||||
}
|
||||
|
||||
// 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'
|
||||
|
@ -1,100 +0,0 @@
|
||||
package server
|
||||
|
||||
import "fmt"
|
||||
|
||||
// TrafficFlowType defines allowed direction of the traffic in the rule
|
||||
type TrafficFlowType int
|
||||
|
||||
const (
|
||||
// TrafficFlowBidirect allows traffic to both direction
|
||||
TrafficFlowBidirect TrafficFlowType = iota
|
||||
// TrafficFlowBidirectString allows traffic to both direction
|
||||
TrafficFlowBidirectString = "bidirect"
|
||||
// DefaultRuleName is a name for the Default rule that is created for every account
|
||||
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
|
||||
type Rule struct {
|
||||
// ID of the rule
|
||||
ID string
|
||||
|
||||
// AccountID is a reference to Account that this object belongs
|
||||
AccountID string `json:"-" gorm:"index"`
|
||||
|
||||
// Name of the rule visible in the UI
|
||||
Name string
|
||||
|
||||
// Description of the rule visible in the UI
|
||||
Description string
|
||||
|
||||
// Disabled status of rule in the system
|
||||
Disabled bool
|
||||
|
||||
// Source list of groups IDs of peers
|
||||
Source []string `gorm:"serializer:json"`
|
||||
|
||||
// Destination list of groups IDs of peers
|
||||
Destination []string `gorm:"serializer:json"`
|
||||
|
||||
// Flow of the traffic allowed by the rule
|
||||
Flow TrafficFlowType
|
||||
}
|
||||
|
||||
func (r *Rule) Copy() *Rule {
|
||||
rule := &Rule{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Disabled: r.Disabled,
|
||||
Source: make([]string, len(r.Source)),
|
||||
Destination: make([]string, len(r.Destination)),
|
||||
Flow: r.Flow,
|
||||
}
|
||||
copy(rule.Source, r.Source)
|
||||
copy(rule.Destination, r.Destination)
|
||||
return rule
|
||||
}
|
||||
|
||||
// EventMeta returns activity event meta related to this rule
|
||||
func (r *Rule) EventMeta() map[string]any {
|
||||
return map[string]any{"name": r.Name}
|
||||
}
|
||||
|
||||
// ToPolicyRule converts a Rule to a PolicyRule object
|
||||
func (r *Rule) ToPolicyRule() *PolicyRule {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return &PolicyRule{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Enabled: !r.Disabled,
|
||||
Description: r.Description,
|
||||
Destinations: r.Destination,
|
||||
Sources: r.Source,
|
||||
Bidirectional: true,
|
||||
Protocol: PolicyRuleProtocolALL,
|
||||
Action: PolicyTrafficActionAccept,
|
||||
}
|
||||
}
|
||||
|
||||
// RuleToPolicy converts a Rule to a Policy query object
|
||||
func RuleToPolicy(rule *Rule) (*Policy, error) {
|
||||
if rule == nil {
|
||||
return nil, fmt.Errorf("rule is empty")
|
||||
}
|
||||
return &Policy{
|
||||
ID: rule.ID,
|
||||
Name: rule.Name,
|
||||
Description: rule.Description,
|
||||
Enabled: !rule.Disabled,
|
||||
Rules: []*PolicyRule{rule.ToPolicyRule()},
|
||||
}, nil
|
||||
}
|
@ -64,7 +64,7 @@ func NewSqliteStore(dataDir string, metrics telemetry.AppMetrics) (*SqliteStore,
|
||||
sql.SetMaxOpenConns(conns) // TODO: make it configurable
|
||||
|
||||
err = db.AutoMigrate(
|
||||
&SetupKey{}, &nbpeer.Peer{}, &User{}, &PersonalAccessToken{}, &Group{}, &Rule{},
|
||||
&SetupKey{}, &nbpeer.Peer{}, &User{}, &PersonalAccessToken{}, &Group{},
|
||||
&Account{}, &Policy{}, &PolicyRule{}, &route.Route{}, &nbdns.NameServerGroup{},
|
||||
&installation{}, &account.ExtraSettings{}, &posture.Checks{}, &nbpeer.NetworkAddress{},
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user