mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-27 09:09:07 +02:00
[client, management] Add port forwarding (#3275)
Add initial support to ingress ports on the client code. - new types where added - new protocol messages and controller
This commit is contained in:
@@ -27,7 +27,8 @@ import (
|
||||
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
||||
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||
"github.com/netbirdio/netbird/management/server/idp"
|
||||
"github.com/netbirdio/netbird/management/server/integrated_validator"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/posture"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
@@ -78,7 +79,7 @@ type AccountManager interface {
|
||||
GetUserByID(ctx context.Context, id string) (*types.User, error)
|
||||
GetUserFromUserAuth(ctx context.Context, userAuth nbcontext.UserAuth) (*types.User, error)
|
||||
ListUsers(ctx context.Context, accountID string) ([]*types.User, error)
|
||||
GetPeers(ctx context.Context, accountID, userID string) ([]*nbpeer.Peer, error)
|
||||
GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error)
|
||||
MarkPeerConnected(ctx context.Context, peerKey string, connected bool, realIP net.IP, accountID string) error
|
||||
DeletePeer(ctx context.Context, accountID, peerID, userID string) error
|
||||
UpdatePeer(ctx context.Context, accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error)
|
||||
@@ -162,6 +163,8 @@ type DefaultAccountManager struct {
|
||||
|
||||
requestBuffer *AccountRequestBuffer
|
||||
|
||||
proxyController port_forwarding.Controller
|
||||
|
||||
// singleAccountMode indicates whether the instance has a single account.
|
||||
// If true, then every new user will end up under the same account.
|
||||
// This value will be set to false if management service has more than one account.
|
||||
@@ -245,6 +248,7 @@ func BuildManager(
|
||||
userDeleteFromIDPEnabled bool,
|
||||
integratedPeerValidator integrated_validator.IntegratedValidator,
|
||||
metrics telemetry.AppMetrics,
|
||||
proxyController port_forwarding.Controller,
|
||||
) (*DefaultAccountManager, error) {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
@@ -267,6 +271,7 @@ func BuildManager(
|
||||
integratedPeerValidator: integratedPeerValidator,
|
||||
metrics: metrics,
|
||||
requestBuffer: NewAccountRequestBuffer(ctx, store),
|
||||
proxyController: proxyController,
|
||||
}
|
||||
accountsCounter, err := store.GetAccountsCounter(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
"github.com/netbirdio/netbird/management/server/util"
|
||||
|
||||
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
||||
@@ -2808,7 +2809,7 @@ func createManager(t TB) (*DefaultAccountManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
manager, err := BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics)
|
||||
manager, err := BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
@@ -208,7 +209,7 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{}, metrics)
|
||||
return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock())
|
||||
}
|
||||
|
||||
func createDNSStore(t *testing.T) (store.Store, error) {
|
||||
|
||||
@@ -645,6 +645,14 @@ func toSyncResponse(ctx context.Context, config *Config, peer *nbpeer.Peer, turn
|
||||
response.NetworkMap.RoutesFirewallRules = routesFirewallRules
|
||||
response.NetworkMap.RoutesFirewallRulesIsEmpty = len(routesFirewallRules) == 0
|
||||
|
||||
if networkMap.ForwardingRules != nil {
|
||||
forwardingRules := make([]*proto.ForwardingRule, 0, len(networkMap.ForwardingRules))
|
||||
for _, rule := range networkMap.ForwardingRules {
|
||||
forwardingRules = append(forwardingRules, rule.ToProto())
|
||||
}
|
||||
response.NetworkMap.ForwardingRules = forwardingRules
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ tags:
|
||||
description: View information about the account and network events.
|
||||
- name: Accounts
|
||||
description: View information about the accounts.
|
||||
- name: Ingress Ports
|
||||
description: Interact with and view information about the ingress peers and ports.
|
||||
x-cloud-only: true
|
||||
components:
|
||||
schemas:
|
||||
Account:
|
||||
@@ -1597,6 +1600,223 @@ components:
|
||||
- initiator_email
|
||||
- target_id
|
||||
- meta
|
||||
IngressPeerCreateRequest:
|
||||
type: object
|
||||
properties:
|
||||
peer_id:
|
||||
description: ID of the peer that is used as an ingress peer
|
||||
type: string
|
||||
example: ch8i4ug6lnn4g9hqv7m0
|
||||
enabled:
|
||||
description: Defines if an ingress peer is enabled
|
||||
type: boolean
|
||||
example: true
|
||||
fallback:
|
||||
description: Defines if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer
|
||||
type: boolean
|
||||
example: true
|
||||
required:
|
||||
- peer_id
|
||||
- enabled
|
||||
- fallback
|
||||
IngressPeerUpdateRequest:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
description: Defines if an ingress peer is enabled
|
||||
type: boolean
|
||||
example: true
|
||||
fallback:
|
||||
description: Defines if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer
|
||||
type: boolean
|
||||
example: true
|
||||
required:
|
||||
- enabled
|
||||
- fallback
|
||||
IngressPeer:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: ID of the ingress peer
|
||||
type: string
|
||||
example: ch8i4ug6lnn4g9hqv7m0
|
||||
peer_id:
|
||||
description: ID of the peer that is used as an ingress peer
|
||||
type: string
|
||||
example: x7p3kqf2rdd8j5zxw4n9
|
||||
ingress_ip:
|
||||
description: Ingress IP address of the ingress peer where the traffic arrives
|
||||
type: string
|
||||
example: 192.34.0.123
|
||||
available_ports:
|
||||
$ref: '#/components/schemas/AvailablePorts'
|
||||
enabled:
|
||||
description: Indicates if an ingress peer is enabled
|
||||
type: boolean
|
||||
example: true
|
||||
connected:
|
||||
description: Indicates if an ingress peer is connected to the management server
|
||||
type: boolean
|
||||
example: true
|
||||
fallback:
|
||||
description: Indicates if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer
|
||||
type: boolean
|
||||
example: true
|
||||
region:
|
||||
description: Region of the ingress peer
|
||||
type: string
|
||||
example: germany
|
||||
required:
|
||||
- id
|
||||
- peer_id
|
||||
- ingress_ip
|
||||
- available_ports
|
||||
- enabled
|
||||
- connected
|
||||
- fallback
|
||||
- region
|
||||
|
||||
AvailablePorts:
|
||||
type: object
|
||||
properties:
|
||||
tcp:
|
||||
description: Number of available TCP ports left on the ingress peer
|
||||
type: integer
|
||||
example: 45765
|
||||
udp:
|
||||
description: Number of available UDP ports left on the ingress peer
|
||||
type: integer
|
||||
example: 50000
|
||||
required:
|
||||
- tcp
|
||||
- udp
|
||||
IngressPortAllocationRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: Name of the ingress port allocation
|
||||
type: string
|
||||
example: Ingress Port Allocation 1
|
||||
enabled:
|
||||
description: Indicates if an ingress port allocation is enabled
|
||||
type: boolean
|
||||
example: true
|
||||
port_ranges:
|
||||
description: List of port ranges that are forwarded by the ingress peer
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/IngressPortAllocationRequestPortRange'
|
||||
direct_port:
|
||||
description: Direct port allocation
|
||||
$ref: '#/components/schemas/IngressPortAllocationRequestDirectPort'
|
||||
required:
|
||||
- name
|
||||
- enabled
|
||||
IngressPortAllocationRequestPortRange:
|
||||
type: object
|
||||
properties:
|
||||
start:
|
||||
description: The starting port of the range of forwarded ports
|
||||
type: integer
|
||||
example: 80
|
||||
end:
|
||||
description: The ending port of the range of forwarded ports
|
||||
type: integer
|
||||
example: 320
|
||||
protocol:
|
||||
description: The protocol accepted by the port range
|
||||
type: string
|
||||
enum: [ "tcp", "udp", "tcp/udp" ]
|
||||
example: tcp
|
||||
required:
|
||||
- start
|
||||
- end
|
||||
- protocol
|
||||
IngressPortAllocationRequestDirectPort:
|
||||
type: object
|
||||
properties:
|
||||
count:
|
||||
description: The number of ports to be forwarded
|
||||
type: integer
|
||||
example: 5
|
||||
protocol:
|
||||
description: The protocol accepted by the port
|
||||
type: string
|
||||
enum: [ "tcp", "udp", "tcp/udp" ]
|
||||
example: udp
|
||||
required:
|
||||
- count
|
||||
- protocol
|
||||
IngressPortAllocation:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: ID of the ingress port allocation
|
||||
type: string
|
||||
example: ch8i4ug6lnn4g9hqv7m0
|
||||
name:
|
||||
description: Name of the ingress port allocation
|
||||
type: string
|
||||
example: Ingress Peer Allocation 1
|
||||
ingress_peer_id:
|
||||
description: ID of the ingress peer that forwards the ports
|
||||
type: string
|
||||
example: x7p3kqf2rdd8j5zxw4n9
|
||||
region:
|
||||
description: Region of the ingress peer
|
||||
type: string
|
||||
example: germany
|
||||
enabled:
|
||||
description: Indicates if an ingress port allocation is enabled
|
||||
type: boolean
|
||||
example: true
|
||||
ingress_ip:
|
||||
description: Ingress IP address of the ingress peer where the traffic arrives
|
||||
type: string
|
||||
example:
|
||||
port_range_mappings:
|
||||
description: List of port ranges that are allowed to be used by the ingress peer
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/IngressPortAllocationPortMapping'
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- ingress_peer_id
|
||||
- region
|
||||
- enabled
|
||||
- ingress_ip
|
||||
- port_range_mappings
|
||||
IngressPortAllocationPortMapping:
|
||||
type: object
|
||||
properties:
|
||||
translated_start:
|
||||
description: The starting port of the translated range of forwarded ports
|
||||
type: integer
|
||||
example: 80
|
||||
translated_end:
|
||||
description: The ending port of the translated range of forwarded ports
|
||||
type: integer
|
||||
example: 320
|
||||
ingress_start:
|
||||
description: The starting port of the range of ingress ports mapped to the forwarded ports
|
||||
type: integer
|
||||
example: 1080
|
||||
ingress_end:
|
||||
description: The ending port of the range of ingress ports mapped to the forwarded ports
|
||||
type: integer
|
||||
example: 1320
|
||||
protocol:
|
||||
description: Protocol accepted by the ports
|
||||
type: string
|
||||
enum: [ "tcp", "udp", "tcp/udp" ]
|
||||
example: tcp
|
||||
required:
|
||||
- translated_start
|
||||
- translated_end
|
||||
- ingress_start
|
||||
- ingress_end
|
||||
- protocol
|
||||
responses:
|
||||
not_found:
|
||||
description: Resource not found
|
||||
@@ -2009,6 +2229,17 @@ paths:
|
||||
summary: List all Peers
|
||||
description: Returns a list of all peers
|
||||
tags: [ Peers ]
|
||||
parameters:
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
type: string
|
||||
description: Filter peers by name
|
||||
- in: query
|
||||
name: ip
|
||||
schema:
|
||||
type: string
|
||||
description: Filter peers by IP address
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
@@ -2152,6 +2383,335 @@ paths:
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/peers/{peerId}/ingress/ports:
|
||||
get:
|
||||
x-cloud-only: true
|
||||
summary: List all Ingress Port Allocations for a Peer
|
||||
description: Returns a list of all ingress port allocations for a peer
|
||||
tags: [ Ingress Ports ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: peerId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of a peer
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
type: string
|
||||
description: Filters ingress port allocations by name
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON Array of Ingress Port Allocations
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/IngressPortAllocation'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
x-cloud-only: true
|
||||
summary: Create a Ingress Port Allocation
|
||||
description: Creates a new ingress port allocation for a peer
|
||||
tags: [ Ingress Ports ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: peerId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of a peer
|
||||
requestBody:
|
||||
description: New Ingress Port Allocation request
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngressPortAllocationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: A Ingress Port Allocation object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngressPortAllocation'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/peers/{peerId}/ingress/ports/{allocationId}:
|
||||
get:
|
||||
x-cloud-only: true
|
||||
summary: Retrieve a Ingress Port Allocation
|
||||
description: Get information about an ingress port allocation
|
||||
tags: [ Ingress Ports ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: peerId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of a peer
|
||||
- in: path
|
||||
name: allocationId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of an ingress port allocation
|
||||
responses:
|
||||
'200':
|
||||
description: A Ingress Port Allocation object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngressPortAllocation'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
x-cloud-only: true
|
||||
parameters:
|
||||
- in: path
|
||||
name: peerId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of a peer
|
||||
- in: path
|
||||
name: allocationId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of an ingress port allocation
|
||||
requestBody:
|
||||
description: update an ingress port allocation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngressPortAllocationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: A Ingress Port Allocation object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngressPortAllocation'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
x-cloud-only: true
|
||||
summary: Delete a Ingress Port Allocation
|
||||
description: Delete an ingress port allocation
|
||||
tags: [ Ingress Ports ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: peerId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of a peer
|
||||
- in: path
|
||||
name: allocationId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of an ingress port allocation
|
||||
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/ingress/peers:
|
||||
get:
|
||||
x-cloud-only: true
|
||||
summary: List all Ingress Peers
|
||||
description: Returns a list of all ingress peers
|
||||
tags: [ Ingress Ports ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON Array of Ingress Peers
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/IngressPeer'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
x-cloud-only: true
|
||||
summary: Create a Ingress Peer
|
||||
description: Creates a new ingress peer
|
||||
tags: [ Ingress Ports ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
requestBody:
|
||||
description: New Ingress Peer request
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngressPeerCreateRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: A Ingress Peer object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngressPeer'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/ingress/peers/{ingressPeerId}:
|
||||
get:
|
||||
x-cloud-only: true
|
||||
summary: Retrieve a Ingress Peer
|
||||
description: Get information about an ingress peer
|
||||
tags: [ Ingress Ports ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: ingressPeerId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of an ingress peer
|
||||
responses:
|
||||
'200':
|
||||
description: A Ingress Peer object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngressPeer'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
x-cloud-only: true
|
||||
summary: Update a Ingress Peer
|
||||
description: Update information about an ingress peer
|
||||
tags: [ Ingress Ports ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: ingressPeerId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of an ingress peer
|
||||
requestBody:
|
||||
description: update an ingress peer
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngressPeerUpdateRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: A Ingress Peer object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IngressPeer'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
x-cloud-only: true
|
||||
summary: Delete a Ingress Peer
|
||||
description: Delete an ingress peer
|
||||
tags: [ Ingress Ports ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: ingressPeerId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The unique identifier of an ingress peer
|
||||
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/setup-keys:
|
||||
get:
|
||||
summary: List all Setup Keys
|
||||
|
||||
@@ -83,6 +83,27 @@ const (
|
||||
GroupMinimumIssuedJwt GroupMinimumIssued = "jwt"
|
||||
)
|
||||
|
||||
// Defines values for IngressPortAllocationPortMappingProtocol.
|
||||
const (
|
||||
IngressPortAllocationPortMappingProtocolTcp IngressPortAllocationPortMappingProtocol = "tcp"
|
||||
IngressPortAllocationPortMappingProtocolTcpudp IngressPortAllocationPortMappingProtocol = "tcp/udp"
|
||||
IngressPortAllocationPortMappingProtocolUdp IngressPortAllocationPortMappingProtocol = "udp"
|
||||
)
|
||||
|
||||
// Defines values for IngressPortAllocationRequestDirectPortProtocol.
|
||||
const (
|
||||
IngressPortAllocationRequestDirectPortProtocolTcp IngressPortAllocationRequestDirectPortProtocol = "tcp"
|
||||
IngressPortAllocationRequestDirectPortProtocolTcpudp IngressPortAllocationRequestDirectPortProtocol = "tcp/udp"
|
||||
IngressPortAllocationRequestDirectPortProtocolUdp IngressPortAllocationRequestDirectPortProtocol = "udp"
|
||||
)
|
||||
|
||||
// Defines values for IngressPortAllocationRequestPortRangeProtocol.
|
||||
const (
|
||||
IngressPortAllocationRequestPortRangeProtocolTcp IngressPortAllocationRequestPortRangeProtocol = "tcp"
|
||||
IngressPortAllocationRequestPortRangeProtocolTcpudp IngressPortAllocationRequestPortRangeProtocol = "tcp/udp"
|
||||
IngressPortAllocationRequestPortRangeProtocolUdp IngressPortAllocationRequestPortRangeProtocol = "udp"
|
||||
)
|
||||
|
||||
// Defines values for NameserverNsType.
|
||||
const (
|
||||
NameserverNsTypeUdp NameserverNsType = "udp"
|
||||
@@ -253,6 +274,15 @@ type AccountSettings struct {
|
||||
RoutingPeerDnsResolutionEnabled *bool `json:"routing_peer_dns_resolution_enabled,omitempty"`
|
||||
}
|
||||
|
||||
// AvailablePorts defines model for AvailablePorts.
|
||||
type AvailablePorts struct {
|
||||
// Tcp Number of available TCP ports left on the ingress peer
|
||||
Tcp int `json:"tcp"`
|
||||
|
||||
// Udp Number of available UDP ports left on the ingress peer
|
||||
Udp int `json:"udp"`
|
||||
}
|
||||
|
||||
// Checks List of objects that perform the actual checks
|
||||
type Checks struct {
|
||||
// GeoLocationCheck Posture check for geo location
|
||||
@@ -426,6 +456,139 @@ type GroupRequest struct {
|
||||
Resources *[]Resource `json:"resources,omitempty"`
|
||||
}
|
||||
|
||||
// IngressPeer defines model for IngressPeer.
|
||||
type IngressPeer struct {
|
||||
AvailablePorts AvailablePorts `json:"available_ports"`
|
||||
|
||||
// Connected Indicates if an ingress peer is connected to the management server
|
||||
Connected bool `json:"connected"`
|
||||
|
||||
// Enabled Indicates if an ingress peer is enabled
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// Fallback Indicates if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer
|
||||
Fallback bool `json:"fallback"`
|
||||
|
||||
// Id ID of the ingress peer
|
||||
Id string `json:"id"`
|
||||
|
||||
// IngressIp Ingress IP address of the ingress peer where the traffic arrives
|
||||
IngressIp string `json:"ingress_ip"`
|
||||
|
||||
// PeerId ID of the peer that is used as an ingress peer
|
||||
PeerId string `json:"peer_id"`
|
||||
|
||||
// Region Region of the ingress peer
|
||||
Region string `json:"region"`
|
||||
}
|
||||
|
||||
// IngressPeerCreateRequest defines model for IngressPeerCreateRequest.
|
||||
type IngressPeerCreateRequest struct {
|
||||
// Enabled Defines if an ingress peer is enabled
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// Fallback Defines if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer
|
||||
Fallback bool `json:"fallback"`
|
||||
|
||||
// PeerId ID of the peer that is used as an ingress peer
|
||||
PeerId string `json:"peer_id"`
|
||||
}
|
||||
|
||||
// IngressPeerUpdateRequest defines model for IngressPeerUpdateRequest.
|
||||
type IngressPeerUpdateRequest struct {
|
||||
// Enabled Defines if an ingress peer is enabled
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// Fallback Defines if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer
|
||||
Fallback bool `json:"fallback"`
|
||||
}
|
||||
|
||||
// IngressPortAllocation defines model for IngressPortAllocation.
|
||||
type IngressPortAllocation struct {
|
||||
// Enabled Indicates if an ingress port allocation is enabled
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// Id ID of the ingress port allocation
|
||||
Id string `json:"id"`
|
||||
|
||||
// IngressIp Ingress IP address of the ingress peer where the traffic arrives
|
||||
IngressIp string `json:"ingress_ip"`
|
||||
|
||||
// IngressPeerId ID of the ingress peer that forwards the ports
|
||||
IngressPeerId string `json:"ingress_peer_id"`
|
||||
|
||||
// Name Name of the ingress port allocation
|
||||
Name string `json:"name"`
|
||||
|
||||
// PortRangeMappings List of port ranges that are allowed to be used by the ingress peer
|
||||
PortRangeMappings []IngressPortAllocationPortMapping `json:"port_range_mappings"`
|
||||
|
||||
// Region Region of the ingress peer
|
||||
Region string `json:"region"`
|
||||
}
|
||||
|
||||
// IngressPortAllocationPortMapping defines model for IngressPortAllocationPortMapping.
|
||||
type IngressPortAllocationPortMapping struct {
|
||||
// IngressEnd The ending port of the range of ingress ports mapped to the forwarded ports
|
||||
IngressEnd int `json:"ingress_end"`
|
||||
|
||||
// IngressStart The starting port of the range of ingress ports mapped to the forwarded ports
|
||||
IngressStart int `json:"ingress_start"`
|
||||
|
||||
// Protocol Protocol accepted by the ports
|
||||
Protocol IngressPortAllocationPortMappingProtocol `json:"protocol"`
|
||||
|
||||
// TranslatedEnd The ending port of the translated range of forwarded ports
|
||||
TranslatedEnd int `json:"translated_end"`
|
||||
|
||||
// TranslatedStart The starting port of the translated range of forwarded ports
|
||||
TranslatedStart int `json:"translated_start"`
|
||||
}
|
||||
|
||||
// IngressPortAllocationPortMappingProtocol Protocol accepted by the ports
|
||||
type IngressPortAllocationPortMappingProtocol string
|
||||
|
||||
// IngressPortAllocationRequest defines model for IngressPortAllocationRequest.
|
||||
type IngressPortAllocationRequest struct {
|
||||
DirectPort *IngressPortAllocationRequestDirectPort `json:"direct_port,omitempty"`
|
||||
|
||||
// Enabled Indicates if an ingress port allocation is enabled
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// Name Name of the ingress port allocation
|
||||
Name string `json:"name"`
|
||||
|
||||
// PortRanges List of port ranges that are forwarded by the ingress peer
|
||||
PortRanges *[]IngressPortAllocationRequestPortRange `json:"port_ranges,omitempty"`
|
||||
}
|
||||
|
||||
// IngressPortAllocationRequestDirectPort defines model for IngressPortAllocationRequestDirectPort.
|
||||
type IngressPortAllocationRequestDirectPort struct {
|
||||
// Count The number of ports to be forwarded
|
||||
Count int `json:"count"`
|
||||
|
||||
// Protocol The protocol accepted by the port
|
||||
Protocol IngressPortAllocationRequestDirectPortProtocol `json:"protocol"`
|
||||
}
|
||||
|
||||
// IngressPortAllocationRequestDirectPortProtocol The protocol accepted by the port
|
||||
type IngressPortAllocationRequestDirectPortProtocol string
|
||||
|
||||
// IngressPortAllocationRequestPortRange defines model for IngressPortAllocationRequestPortRange.
|
||||
type IngressPortAllocationRequestPortRange struct {
|
||||
// End The ending port of the range of forwarded ports
|
||||
End int `json:"end"`
|
||||
|
||||
// Protocol The protocol accepted by the port range
|
||||
Protocol IngressPortAllocationRequestPortRangeProtocol `json:"protocol"`
|
||||
|
||||
// Start The starting port of the range of forwarded ports
|
||||
Start int `json:"start"`
|
||||
}
|
||||
|
||||
// IngressPortAllocationRequestPortRangeProtocol The protocol accepted by the port range
|
||||
type IngressPortAllocationRequestPortRangeProtocol string
|
||||
|
||||
// Location Describe geographical location information
|
||||
type Location struct {
|
||||
// CityName Commonly used English name of the city
|
||||
@@ -1466,6 +1629,21 @@ type UserRequest struct {
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// GetApiPeersParams defines parameters for GetApiPeers.
|
||||
type GetApiPeersParams struct {
|
||||
// Name Filter peers by name
|
||||
Name *string `form:"name,omitempty" json:"name,omitempty"`
|
||||
|
||||
// Ip Filter peers by IP address
|
||||
Ip *string `form:"ip,omitempty" json:"ip,omitempty"`
|
||||
}
|
||||
|
||||
// GetApiPeersPeerIdIngressPortsParams defines parameters for GetApiPeersPeerIdIngressPorts.
|
||||
type GetApiPeersPeerIdIngressPortsParams struct {
|
||||
// Name Filters ingress port allocations by name
|
||||
Name *string `form:"name,omitempty" json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// GetApiUsersParams defines parameters for GetApiUsers.
|
||||
type GetApiUsersParams struct {
|
||||
// ServiceUser Filters users and returns either regular users or service users
|
||||
@@ -1490,6 +1668,12 @@ type PostApiGroupsJSONRequestBody = GroupRequest
|
||||
// PutApiGroupsGroupIdJSONRequestBody defines body for PutApiGroupsGroupId for application/json ContentType.
|
||||
type PutApiGroupsGroupIdJSONRequestBody = GroupRequest
|
||||
|
||||
// PostApiIngressPeersJSONRequestBody defines body for PostApiIngressPeers for application/json ContentType.
|
||||
type PostApiIngressPeersJSONRequestBody = IngressPeerCreateRequest
|
||||
|
||||
// PutApiIngressPeersIngressPeerIdJSONRequestBody defines body for PutApiIngressPeersIngressPeerId for application/json ContentType.
|
||||
type PutApiIngressPeersIngressPeerIdJSONRequestBody = IngressPeerUpdateRequest
|
||||
|
||||
// PostApiNetworksJSONRequestBody defines body for PostApiNetworks for application/json ContentType.
|
||||
type PostApiNetworksJSONRequestBody = NetworkRequest
|
||||
|
||||
@@ -1511,6 +1695,12 @@ type PutApiNetworksNetworkIdRoutersRouterIdJSONRequestBody = NetworkRouterReques
|
||||
// PutApiPeersPeerIdJSONRequestBody defines body for PutApiPeersPeerId for application/json ContentType.
|
||||
type PutApiPeersPeerIdJSONRequestBody = PeerRequest
|
||||
|
||||
// PostApiPeersPeerIdIngressPortsJSONRequestBody defines body for PostApiPeersPeerIdIngressPorts for application/json ContentType.
|
||||
type PostApiPeersPeerIdIngressPortsJSONRequestBody = IngressPortAllocationRequest
|
||||
|
||||
// PutApiPeersPeerIdIngressPortsAllocationIdJSONRequestBody defines body for PutApiPeersPeerIdIngressPortsAllocationId for application/json ContentType.
|
||||
type PutApiPeersPeerIdIngressPortsAllocationIdJSONRequestBody = IngressPortAllocationRequest
|
||||
|
||||
// PostApiPoliciesJSONRequestBody defines body for PostApiPolicies for application/json ContentType.
|
||||
type PostApiPoliciesJSONRequestBody = PolicyUpdate
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
"github.com/netbirdio/netbird/management/server/permissions"
|
||||
|
||||
s "github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/auth"
|
||||
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||
@@ -25,10 +28,11 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/http/handlers/setup_keys"
|
||||
"github.com/netbirdio/netbird/management/server/http/handlers/users"
|
||||
"github.com/netbirdio/netbird/management/server/http/middleware"
|
||||
"github.com/netbirdio/netbird/management/server/integrated_validator"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
||||
nbnetworks "github.com/netbirdio/netbird/management/server/networks"
|
||||
"github.com/netbirdio/netbird/management/server/networks/resources"
|
||||
"github.com/netbirdio/netbird/management/server/networks/routers"
|
||||
nbpeers "github.com/netbirdio/netbird/management/server/peers"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
)
|
||||
|
||||
@@ -45,8 +49,11 @@ func NewAPIHandler(
|
||||
LocationManager geolocation.Geolocation,
|
||||
authManager auth.Manager,
|
||||
appMetrics telemetry.AppMetrics,
|
||||
config *s.Config,
|
||||
integratedValidator integrated_validator.IntegratedValidator) (http.Handler, error) {
|
||||
integratedValidator integrated_validator.IntegratedValidator,
|
||||
proxyController port_forwarding.Controller,
|
||||
permissionsManager permissions.Manager,
|
||||
peersManager nbpeers.Manager,
|
||||
) (http.Handler, error) {
|
||||
|
||||
authMiddleware := middleware.NewAuthMiddleware(
|
||||
authManager,
|
||||
@@ -66,7 +73,7 @@ func NewAPIHandler(
|
||||
|
||||
router.Use(metricsMiddleware.Handler, corsMiddleware.Handler, authMiddleware.Handler, acMiddleware.Handler)
|
||||
|
||||
if _, err := integrations.RegisterHandlers(ctx, prefix, router, accountManager, integratedValidator, appMetrics.GetMeter()); err != nil {
|
||||
if _, err := integrations.RegisterHandlers(ctx, prefix, router, accountManager, integratedValidator, appMetrics.GetMeter(), permissionsManager, peersManager, proxyController); err != nil {
|
||||
return nil, fmt.Errorf("register integrations endpoints: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ func (h *handler) getAllGroups(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID)
|
||||
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "")
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
@@ -149,7 +149,7 @@ func (h *handler) updateGroup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID)
|
||||
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "")
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
@@ -209,7 +209,7 @@ func (h *handler) createGroup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID)
|
||||
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "")
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
@@ -270,7 +270,7 @@ func (h *handler) getGroup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID)
|
||||
accountPeers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, "", "")
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
|
||||
@@ -66,7 +66,7 @@ func initGroupTestData(initGroups ...*types.Group) *handler {
|
||||
|
||||
return nil, fmt.Errorf("unknown group name")
|
||||
},
|
||||
GetPeersFunc: func(ctx context.Context, accountID, userID string) ([]*nbpeer.Peer, error) {
|
||||
GetPeersFunc: func(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) {
|
||||
return maps.Values(TestPeers), nil
|
||||
},
|
||||
DeleteGroupFunc: func(_ context.Context, accountID, userId, groupID string) error {
|
||||
|
||||
@@ -180,9 +180,12 @@ func (h *Handler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
nameFilter := r.URL.Query().Get("name")
|
||||
ipFilter := r.URL.Query().Get("ip")
|
||||
|
||||
accountID, userID := userAuth.AccountId, userAuth.UserId
|
||||
|
||||
peers, err := h.accountManager.GetPeers(r.Context(), accountID, userID)
|
||||
peers, err := h.accountManager.GetPeers(r.Context(), accountID, userID, nameFilter, ipFilter)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
|
||||
@@ -122,7 +122,7 @@ func initTestMetaData(peers ...*nbpeer.Peer) *Handler {
|
||||
}
|
||||
return p, nil
|
||||
},
|
||||
GetPeersFunc: func(_ context.Context, accountID, userID string) ([]*nbpeer.Peer, error) {
|
||||
GetPeersFunc: func(_ context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) {
|
||||
return peers, nil
|
||||
},
|
||||
GetPeerGroupsFunc: func(ctx context.Context, accountID, peerID string) ([]*types.Group, error) {
|
||||
|
||||
@@ -15,9 +15,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/peers"
|
||||
"github.com/netbirdio/netbird/management/server/permissions"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/auth"
|
||||
@@ -112,7 +116,8 @@ func BuildApiBlackBoxWithDBState(t TB, sqlFile string, expectedPeerUpdate *serve
|
||||
|
||||
geoMock := &geolocation.Mock{}
|
||||
validatorMock := server.MocIntegratedValidator{}
|
||||
am, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics)
|
||||
proxyController := integrations.NewController(store)
|
||||
am, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, proxyController)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create manager: %v", err)
|
||||
}
|
||||
@@ -130,7 +135,10 @@ func BuildApiBlackBoxWithDBState(t TB, sqlFile string, expectedPeerUpdate *serve
|
||||
resourcesManagerMock := resources.NewManagerMock()
|
||||
routersManagerMock := routers.NewManagerMock()
|
||||
groupsManagerMock := groups.NewManagerMock()
|
||||
apiHandler, err := nbhttp.NewAPIHandler(context.Background(), am, networksManagerMock, resourcesManagerMock, routersManagerMock, groupsManagerMock, geoMock, authManagerMock, metrics, &server.Config{}, validatorMock)
|
||||
permissionsManagerMock := permissions.NewManagerMock()
|
||||
peersManager := peers.NewManager(store, permissionsManagerMock)
|
||||
|
||||
apiHandler, err := nbhttp.NewAPIHandler(context.Background(), am, networksManagerMock, resourcesManagerMock, routersManagerMock, groupsManagerMock, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManagerMock, peersManager)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create API handler: %v", err)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func (am *DefaultAccountManager) GetValidatedPeers(ctx context.Context, accountI
|
||||
return err
|
||||
}
|
||||
|
||||
peers, err = transaction.GetAccountPeers(ctx, store.LockingStrengthShare, accountID)
|
||||
peers, err = transaction.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "")
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package port_forwarding
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
nbtypes "github.com/netbirdio/netbird/management/server/types"
|
||||
)
|
||||
|
||||
type Controller interface {
|
||||
SendUpdate(ctx context.Context, accountID string, affectedProxyID string, affectedPeerIDs []string)
|
||||
GetProxyNetworkMaps(ctx context.Context, accountID string) (map[string]*nbtypes.NetworkMap, error)
|
||||
IsPeerInIngressPorts(ctx context.Context, accountID, peerID string) (bool, error)
|
||||
}
|
||||
|
||||
type ControllerMock struct {
|
||||
}
|
||||
|
||||
func NewControllerMock() *ControllerMock {
|
||||
return &ControllerMock{}
|
||||
}
|
||||
|
||||
func (c *ControllerMock) SendUpdate(ctx context.Context, accountID string, affectedProxyID string, affectedPeerIDs []string) {
|
||||
// noop
|
||||
}
|
||||
|
||||
func (c *ControllerMock) GetProxyNetworkMaps(ctx context.Context, accountID string) (map[string]*nbtypes.NetworkMap, error) {
|
||||
return make(map[string]*nbtypes.NetworkMap), nil
|
||||
}
|
||||
|
||||
func (c *ControllerMock) IsPeerInIngressPorts(ctx context.Context, accountID, peerID string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/netbirdio/netbird/formatter/hook"
|
||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
"github.com/netbirdio/netbird/management/server/settings"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
@@ -430,7 +431,7 @@ func startManagementForTest(t *testing.T, testFile string, config *Config) (*grp
|
||||
require.NoError(t, err)
|
||||
|
||||
accountManager, err := BuildManager(ctx, store, peersUpdateManager, nil, "", "netbird.selfhosted",
|
||||
eventStore, nil, false, MocIntegratedValidator{}, metrics)
|
||||
eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock())
|
||||
|
||||
if err != nil {
|
||||
cleanup()
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
"github.com/netbirdio/netbird/management/server/settings"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
@@ -189,6 +190,7 @@ func startServer(
|
||||
false,
|
||||
server.MocIntegratedValidator{},
|
||||
metrics,
|
||||
port_forwarding.NewControllerMock(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("failed creating an account manager: %v", err)
|
||||
|
||||
@@ -33,7 +33,7 @@ type MockAccountManager struct {
|
||||
GetAccountIDByUserIdFunc func(ctx context.Context, userId, domain string) (string, error)
|
||||
GetUserFromUserAuthFunc func(ctx context.Context, userAuth nbcontext.UserAuth) (*types.User, error)
|
||||
ListUsersFunc func(ctx context.Context, accountID string) ([]*types.User, error)
|
||||
GetPeersFunc func(ctx context.Context, accountID, userID string) ([]*nbpeer.Peer, error)
|
||||
GetPeersFunc func(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error)
|
||||
MarkPeerConnectedFunc func(ctx context.Context, peerKey string, connected bool, realIP net.IP) error
|
||||
SyncAndMarkPeerFunc func(ctx context.Context, accountID string, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
|
||||
DeletePeerFunc func(ctx context.Context, accountID, peerKey, userID string) error
|
||||
@@ -605,9 +605,9 @@ func (am *MockAccountManager) GetAccountIDFromUserAuth(ctx context.Context, user
|
||||
}
|
||||
|
||||
// GetPeers mocks GetPeers of the AccountManager interface
|
||||
func (am *MockAccountManager) GetPeers(ctx context.Context, accountID, userID string) ([]*nbpeer.Peer, error) {
|
||||
func (am *MockAccountManager) GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) {
|
||||
if am.GetPeersFunc != nil {
|
||||
return am.GetPeersFunc(ctx, accountID, userID)
|
||||
return am.GetPeersFunc(ctx, accountID, userID, nameFilter, ipFilter)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeers is not implemented")
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
@@ -771,7 +772,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics)
|
||||
return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock())
|
||||
}
|
||||
|
||||
func createNSStore(t *testing.T) (store.Store, error) {
|
||||
|
||||
@@ -61,7 +61,7 @@ type PeerLogin struct {
|
||||
|
||||
// GetPeers returns a list of peers under the given account filtering out peers that do not belong to a user if
|
||||
// the current user is not an admin.
|
||||
func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID string) ([]*nbpeer.Peer, error) {
|
||||
func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) {
|
||||
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -80,7 +80,7 @@ func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID
|
||||
return []*nbpeer.Peer{}, nil
|
||||
}
|
||||
|
||||
accountPeers, err := am.Store.GetAccountPeers(ctx, store.LockingStrengthShare, accountID)
|
||||
accountPeers, err := am.Store.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, nameFilter, ipFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -375,6 +375,10 @@ func (am *DefaultAccountManager) DeletePeer(ctx context.Context, accountID, peer
|
||||
return err
|
||||
}
|
||||
|
||||
if err = am.validatePeerDelete(ctx, accountID, peerID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateAccountPeers, err = isPeerInActiveGroup(ctx, transaction, accountID, peerID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -437,7 +441,21 @@ func (am *DefaultAccountManager) GetNetworkMap(ctx context.Context, peerID strin
|
||||
return nil, err
|
||||
}
|
||||
customZone := account.GetPeersCustomZone(ctx, am.dnsDomain)
|
||||
return account.GetPeerNetworkMap(ctx, peer.ID, customZone, validatedPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil), nil
|
||||
|
||||
proxyNetworkMaps, err := am.proxyController.GetProxyNetworkMaps(ctx, account.Id)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkMap := account.GetPeerNetworkMap(ctx, peer.ID, customZone, validatedPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil)
|
||||
|
||||
proxyNetworkMap, ok := proxyNetworkMaps[peer.ID]
|
||||
if ok {
|
||||
networkMap.Merge(proxyNetworkMap)
|
||||
}
|
||||
|
||||
return networkMap, nil
|
||||
}
|
||||
|
||||
// GetPeerNetwork returns the Network for a given peer
|
||||
@@ -1034,7 +1052,21 @@ func (am *DefaultAccountManager) getValidatedPeerWithMap(ctx context.Context, is
|
||||
}
|
||||
|
||||
customZone := account.GetPeersCustomZone(ctx, am.dnsDomain)
|
||||
return peer, account.GetPeerNetworkMap(ctx, peer.ID, customZone, approvedPeersMap, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), am.metrics.AccountManagerMetrics()), postureChecks, nil
|
||||
|
||||
proxyNetworkMaps, err := am.proxyController.GetProxyNetworkMaps(ctx, account.Id)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
networkMap := account.GetPeerNetworkMap(ctx, peer.ID, customZone, approvedPeersMap, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), am.metrics.AccountManagerMetrics())
|
||||
|
||||
proxyNetworkMap, ok := proxyNetworkMaps[peer.ID]
|
||||
if ok {
|
||||
networkMap.Merge(proxyNetworkMap)
|
||||
}
|
||||
|
||||
return peer, networkMap, postureChecks, nil
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) handleExpiredPeer(ctx context.Context, transaction store.Store, user *types.User, peer *nbpeer.Peer) error {
|
||||
@@ -1174,6 +1206,12 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
|
||||
proxyNetworkMaps, err := am.proxyController.GetProxyNetworkMaps(ctx, accountID)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, peer := range account.Peers {
|
||||
if !am.peersUpdateManager.HasChannel(peer.ID) {
|
||||
log.WithContext(ctx).Tracef("peer %s doesn't have a channel, skipping network map update", peer.ID)
|
||||
@@ -1193,11 +1231,19 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account
|
||||
}
|
||||
|
||||
remotePeerNetworkMap := account.GetPeerNetworkMap(ctx, p.ID, customZone, approvedPeersMap, resourcePolicies, routers, am.metrics.AccountManagerMetrics())
|
||||
|
||||
proxyNetworkMap, ok := proxyNetworkMaps[p.ID]
|
||||
if ok {
|
||||
remotePeerNetworkMap.Merge(proxyNetworkMap)
|
||||
}
|
||||
|
||||
update := toSyncResponse(ctx, nil, p, nil, nil, remotePeerNetworkMap, am.GetDNSDomain(), postureChecks, dnsCache, account.Settings.RoutingPeerDNSResolutionEnabled)
|
||||
am.peersUpdateManager.SendUpdate(ctx, p.ID, &UpdateMessage{Update: update, NetworkMap: remotePeerNetworkMap})
|
||||
}(peer)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
wg.Wait()
|
||||
if am.metrics != nil {
|
||||
am.metrics.AccountManagerMetrics().CountUpdateAccountPeersDuration(time.Since(start))
|
||||
@@ -1241,7 +1287,19 @@ func (am *DefaultAccountManager) UpdateAccountPeer(ctx context.Context, accountI
|
||||
return
|
||||
}
|
||||
|
||||
proxyNetworkMaps, err := am.proxyController.GetProxyNetworkMaps(ctx, accountId)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
remotePeerNetworkMap := account.GetPeerNetworkMap(ctx, peerId, customZone, approvedPeersMap, resourcePolicies, routers, am.metrics.AccountManagerMetrics())
|
||||
|
||||
proxyNetworkMap, ok := proxyNetworkMaps[peer.ID]
|
||||
if ok {
|
||||
remotePeerNetworkMap.Merge(proxyNetworkMap)
|
||||
}
|
||||
|
||||
update := toSyncResponse(ctx, nil, peer, nil, nil, remotePeerNetworkMap, am.GetDNSDomain(), postureChecks, dnsCache, account.Settings.RoutingPeerDNSResolutionEnabled)
|
||||
am.peersUpdateManager.SendUpdate(ctx, peer.ID, &UpdateMessage{Update: update, NetworkMap: remotePeerNetworkMap})
|
||||
}
|
||||
@@ -1471,3 +1529,17 @@ func ConvertSliceToMap(existingLabels []string) map[string]struct{} {
|
||||
}
|
||||
return labelMap
|
||||
}
|
||||
|
||||
// validatePeerDelete checks if the peer can be deleted.
|
||||
func (am *DefaultAccountManager) validatePeerDelete(ctx context.Context, accountId, peerId string) error {
|
||||
linkedInIngressPorts, err := am.proxyController.IsPeerInIngressPorts(ctx, accountId, peerId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if linkedInIngressPorts {
|
||||
return status.Errorf(status.PreconditionFailed, "peer is linked to ingress ports: %s", peerId)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
"github.com/netbirdio/netbird/management/server/util"
|
||||
|
||||
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
||||
@@ -707,7 +708,7 @@ func TestDefaultAccountManager_GetPeers(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
peers, err := manager.GetPeers(context.Background(), accountID, someUser)
|
||||
peers, err := manager.GetPeers(context.Background(), accountID, someUser, "", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
@@ -913,7 +914,7 @@ func BenchmarkGetPeers(b *testing.B) {
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := manager.GetPeers(context.Background(), accountID, userID)
|
||||
_, err := manager.GetPeers(context.Background(), accountID, userID, "", "")
|
||||
if err != nil {
|
||||
b.Fatalf("GetPeers failed: %v", err)
|
||||
}
|
||||
@@ -1079,6 +1080,20 @@ func TestToSyncResponse(t *testing.T) {
|
||||
FirewallRules: []*types.FirewallRule{
|
||||
{PeerIP: "192.168.1.2", Direction: types.FirewallRuleDirectionIN, Action: string(types.PolicyTrafficActionAccept), Protocol: string(types.PolicyRuleProtocolTCP), Port: "80"},
|
||||
},
|
||||
ForwardingRules: []*types.ForwardingRule{
|
||||
{
|
||||
RuleProtocol: "tcp",
|
||||
DestinationPorts: types.RulePortRange{
|
||||
Start: 1000,
|
||||
End: 2000,
|
||||
},
|
||||
TranslatedAddress: net.IPv4(192, 168, 1, 2),
|
||||
TranslatedPorts: types.RulePortRange{
|
||||
Start: 11000,
|
||||
End: 12000,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
dnsName := "example.com"
|
||||
checks := []*posture.Checks{
|
||||
@@ -1170,6 +1185,14 @@ func TestToSyncResponse(t *testing.T) {
|
||||
// assert posture checks
|
||||
assert.Equal(t, 1, len(response.Checks))
|
||||
assert.Equal(t, "/usr/bin/netbird", response.Checks[0].Files[0])
|
||||
// assert network map ForwardingRules
|
||||
assert.Equal(t, 1, len(response.NetworkMap.ForwardingRules))
|
||||
assert.Equal(t, proto.RuleProtocol_TCP, response.NetworkMap.ForwardingRules[0].Protocol)
|
||||
assert.Equal(t, uint32(1000), response.NetworkMap.ForwardingRules[0].DestinationPort.GetRange().Start)
|
||||
assert.Equal(t, uint32(2000), response.NetworkMap.ForwardingRules[0].DestinationPort.GetRange().End)
|
||||
assert.Equal(t, net.IPv4(192, 168, 1, 2).To4(), net.IP(response.NetworkMap.ForwardingRules[0].TranslatedAddress))
|
||||
assert.Equal(t, uint32(11000), response.NetworkMap.ForwardingRules[0].TranslatedPort.GetRange().Start)
|
||||
assert.Equal(t, uint32(12000), response.NetworkMap.ForwardingRules[0].TranslatedPort.GetRange().End)
|
||||
}
|
||||
|
||||
func Test_RegisterPeerByUser(t *testing.T) {
|
||||
@@ -1188,7 +1211,7 @@ func Test_RegisterPeerByUser(t *testing.T) {
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics)
|
||||
am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock())
|
||||
assert.NoError(t, err)
|
||||
|
||||
existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
|
||||
@@ -1252,7 +1275,7 @@ func Test_RegisterPeerBySetupKey(t *testing.T) {
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics)
|
||||
am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock())
|
||||
assert.NoError(t, err)
|
||||
|
||||
existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
|
||||
@@ -1319,7 +1342,7 @@ func Test_RegisterPeerRollbackOnFailure(t *testing.T) {
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics)
|
||||
am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock())
|
||||
assert.NoError(t, err)
|
||||
|
||||
existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package peers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/permissions"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
)
|
||||
|
||||
type Manager interface {
|
||||
GetPeer(ctx context.Context, accountID, userID, peerID string) (*peer.Peer, error)
|
||||
GetAllPeers(ctx context.Context, accountID, userID string) ([]*peer.Peer, error)
|
||||
}
|
||||
|
||||
type managerImpl struct {
|
||||
store store.Store
|
||||
permissionsManager permissions.Manager
|
||||
}
|
||||
|
||||
func NewManager(store store.Store, permissionsManager permissions.Manager) Manager {
|
||||
return &managerImpl{
|
||||
store: store,
|
||||
permissionsManager: permissionsManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *managerImpl) GetPeer(ctx context.Context, accountID, userID, peerID string) (*peer.Peer, error) {
|
||||
allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Peers, permissions.Read)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate user permissions: %w", err)
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
return nil, status.NewPermissionDeniedError()
|
||||
}
|
||||
|
||||
return m.store.GetPeerByID(ctx, store.LockingStrengthShare, accountID, peerID)
|
||||
}
|
||||
|
||||
func (m *managerImpl) GetAllPeers(ctx context.Context, accountID, userID string) ([]*peer.Peer, error) {
|
||||
allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Peers, permissions.Read)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate user permissions: %w", err)
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
return nil, status.NewPermissionDeniedError()
|
||||
}
|
||||
|
||||
return m.store.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "")
|
||||
}
|
||||
@@ -261,6 +261,7 @@ func toProtocolFirewallRules(rules []*types.FirewallRule) []*proto.FirewallRule
|
||||
Action: getProtoAction(rule.Action),
|
||||
Protocol: getProtoProtocol(rule.Protocol),
|
||||
Port: rule.Port,
|
||||
PortInfo: rule.PortRange.ToProto(),
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -284,7 +284,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
||||
for _, rule := range firewallRules {
|
||||
contains := false
|
||||
for _, expectedRule := range epectedFirewallRules {
|
||||
if rule.IsEqual(expectedRule) {
|
||||
if rule.Equal(expectedRule) {
|
||||
contains = true
|
||||
break
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/netbirdio/netbird/management/domain"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
||||
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
||||
networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
|
||||
@@ -457,7 +458,7 @@ func TestCreateRoute(t *testing.T) {
|
||||
// assign generated ID
|
||||
testCase.expectedRoute.ID = outRoute.ID
|
||||
|
||||
if !testCase.expectedRoute.IsEqual(outRoute) {
|
||||
if !testCase.expectedRoute.Equal(outRoute) {
|
||||
t.Errorf("new route didn't match expected route:\nGot %#v\nExpected:%#v\n", outRoute, testCase.expectedRoute)
|
||||
}
|
||||
})
|
||||
@@ -998,7 +999,7 @@ func TestSaveRoute(t *testing.T) {
|
||||
savedRoute, saved := account.Routes[testCase.expectedRoute.ID]
|
||||
require.True(t, saved)
|
||||
|
||||
if !testCase.expectedRoute.IsEqual(savedRoute) {
|
||||
if !testCase.expectedRoute.Equal(savedRoute) {
|
||||
t.Errorf("new route didn't match expected route:\nGot %#v\nExpected:%#v\n", savedRoute, testCase.expectedRoute)
|
||||
}
|
||||
})
|
||||
@@ -1192,7 +1193,7 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
|
||||
peer1Routes, err := am.GetNetworkMap(context.Background(), peer1ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer1Routes.Routes, 1, "we should receive one route for peer1")
|
||||
require.True(t, expectedRoute.IsEqual(peer1Routes.Routes[0]), "received route should be equal")
|
||||
require.True(t, expectedRoute.Equal(peer1Routes.Routes[0]), "received route should be equal")
|
||||
|
||||
peer2Routes, err := am.GetNetworkMap(context.Background(), peer2ID)
|
||||
require.NoError(t, err)
|
||||
@@ -1204,7 +1205,7 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
|
||||
peer2Routes, err = am.GetNetworkMap(context.Background(), peer2ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer2Routes.Routes, 1, "we should receive one route")
|
||||
require.True(t, peer1Routes.Routes[0].IsEqual(peer2Routes.Routes[0]), "routes should be the same for peers in the same group")
|
||||
require.True(t, peer1Routes.Routes[0].Equal(peer2Routes.Routes[0]), "routes should be the same for peers in the same group")
|
||||
|
||||
newGroup := &types.Group{
|
||||
ID: xid.New().String(),
|
||||
@@ -1256,7 +1257,7 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics)
|
||||
return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock())
|
||||
}
|
||||
|
||||
func createRouterStore(t *testing.T) (store.Store, error) {
|
||||
|
||||
@@ -1262,10 +1262,18 @@ func (s *SqlStore) GetPeerGroups(ctx context.Context, lockStrength LockingStreng
|
||||
}
|
||||
|
||||
// GetAccountPeers retrieves peers for an account.
|
||||
func (s *SqlStore) GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*nbpeer.Peer, error) {
|
||||
func (s *SqlStore) GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) {
|
||||
var peers []*nbpeer.Peer
|
||||
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).Find(&peers, accountIDCondition, accountID)
|
||||
if err := result.Error; err != nil {
|
||||
query := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).Where(accountIDCondition, accountID)
|
||||
|
||||
if nameFilter != "" {
|
||||
query = query.Where("name LIKE ?", "%"+nameFilter+"%")
|
||||
}
|
||||
if ipFilter != "" {
|
||||
query = query.Where("ip LIKE ?", "%"+ipFilter+"%")
|
||||
}
|
||||
|
||||
if err := query.Find(&peers).Error; err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get peers from the store: %s", err)
|
||||
return nil, status.Errorf(status.Internal, "failed to get peers from store")
|
||||
}
|
||||
|
||||
@@ -2585,6 +2585,8 @@ func TestSqlStore_GetAccountPeers(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
accountID string
|
||||
nameFilter string
|
||||
ipFilter string
|
||||
expectedCount int
|
||||
}{
|
||||
{
|
||||
@@ -2602,11 +2604,29 @@ func TestSqlStore_GetAccountPeers(t *testing.T) {
|
||||
accountID: "",
|
||||
expectedCount: 0,
|
||||
},
|
||||
{
|
||||
name: "should filter peers by name",
|
||||
accountID: "bf1c8084-ba50-4ce7-9439-34653001fc3b",
|
||||
nameFilter: "expiredhost",
|
||||
expectedCount: 1,
|
||||
},
|
||||
{
|
||||
name: "should filter peers by partial name",
|
||||
accountID: "bf1c8084-ba50-4ce7-9439-34653001fc3b",
|
||||
nameFilter: "host",
|
||||
expectedCount: 3,
|
||||
},
|
||||
{
|
||||
name: "should filter peers by ip",
|
||||
accountID: "bf1c8084-ba50-4ce7-9439-34653001fc3b",
|
||||
ipFilter: "100.64.39.54",
|
||||
expectedCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
peers, err := store.GetAccountPeers(context.Background(), LockingStrengthShare, tt.accountID)
|
||||
peers, err := store.GetAccountPeers(context.Background(), LockingStrengthShare, tt.accountID, tt.nameFilter, tt.ipFilter)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peers, tt.expectedCount)
|
||||
})
|
||||
|
||||
@@ -120,7 +120,7 @@ type Store interface {
|
||||
RemoveResourceFromGroup(ctx context.Context, accountId string, groupID string, resourceID string) error
|
||||
AddPeerToAccount(ctx context.Context, lockStrength LockingStrength, peer *nbpeer.Peer) error
|
||||
GetPeerByPeerPubKey(ctx context.Context, lockStrength LockingStrength, peerKey string) (*nbpeer.Peer, error)
|
||||
GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*nbpeer.Peer, error)
|
||||
GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error)
|
||||
GetUserPeers(ctx context.Context, lockStrength LockingStrength, accountID, userID string) ([]*nbpeer.Peer, error)
|
||||
GetPeerByID(ctx context.Context, lockStrength LockingStrength, accountID string, peerID string) (*nbpeer.Peer, error)
|
||||
GetPeersByIDs(ctx context.Context, lockStrength LockingStrength, accountID string, peerIDs []string) (map[string]*nbpeer.Peer, error)
|
||||
|
||||
@@ -3,6 +3,7 @@ package types
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -33,15 +34,14 @@ type FirewallRule struct {
|
||||
|
||||
// Port of the traffic
|
||||
Port string
|
||||
|
||||
// PortRange represents the range of ports for a firewall rule
|
||||
PortRange RulePortRange
|
||||
}
|
||||
|
||||
// IsEqual checks if two firewall rules are equal.
|
||||
func (r *FirewallRule) IsEqual(other *FirewallRule) bool {
|
||||
return r.PeerIP == other.PeerIP &&
|
||||
r.Direction == other.Direction &&
|
||||
r.Action == other.Action &&
|
||||
r.Protocol == other.Protocol &&
|
||||
r.Port == other.Port
|
||||
// Equal checks if two firewall rules are equal.
|
||||
func (r *FirewallRule) Equal(other *FirewallRule) bool {
|
||||
return reflect.DeepEqual(r, other)
|
||||
}
|
||||
|
||||
// generateRouteFirewallRules generates a list of firewall rules for a given route.
|
||||
|
||||
@@ -8,10 +8,13 @@ import (
|
||||
|
||||
"github.com/c-robinson/iplib"
|
||||
"github.com/rs/xid"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/proto"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
"github.com/netbirdio/netbird/management/server/util"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
)
|
||||
|
||||
@@ -33,6 +36,73 @@ type NetworkMap struct {
|
||||
OfflinePeers []*nbpeer.Peer
|
||||
FirewallRules []*FirewallRule
|
||||
RoutesFirewallRules []*RouteFirewallRule
|
||||
ForwardingRules []*ForwardingRule
|
||||
}
|
||||
|
||||
func (nm *NetworkMap) Merge(other *NetworkMap) {
|
||||
nm.Peers = mergeUniquePeersByID(nm.Peers, other.Peers)
|
||||
nm.Routes = util.MergeUnique(nm.Routes, other.Routes)
|
||||
nm.OfflinePeers = mergeUniquePeersByID(nm.OfflinePeers, other.OfflinePeers)
|
||||
nm.FirewallRules = util.MergeUnique(nm.FirewallRules, other.FirewallRules)
|
||||
nm.RoutesFirewallRules = util.MergeUnique(nm.RoutesFirewallRules, other.RoutesFirewallRules)
|
||||
nm.ForwardingRules = util.MergeUnique(nm.ForwardingRules, other.ForwardingRules)
|
||||
}
|
||||
|
||||
func mergeUniquePeersByID(peers1, peers2 []*nbpeer.Peer) []*nbpeer.Peer {
|
||||
result := make(map[string]*nbpeer.Peer)
|
||||
for _, peer := range peers1 {
|
||||
result[peer.ID] = peer
|
||||
}
|
||||
for _, peer := range peers2 {
|
||||
if _, ok := result[peer.ID]; !ok {
|
||||
result[peer.ID] = peer
|
||||
}
|
||||
}
|
||||
|
||||
return maps.Values(result)
|
||||
}
|
||||
|
||||
type ForwardingRule struct {
|
||||
RuleProtocol string
|
||||
DestinationPorts RulePortRange
|
||||
TranslatedAddress net.IP
|
||||
TranslatedPorts RulePortRange
|
||||
}
|
||||
|
||||
func (f *ForwardingRule) ToProto() *proto.ForwardingRule {
|
||||
var protocol proto.RuleProtocol
|
||||
switch f.RuleProtocol {
|
||||
case "icmp":
|
||||
protocol = proto.RuleProtocol_ICMP
|
||||
case "tcp":
|
||||
protocol = proto.RuleProtocol_TCP
|
||||
case "udp":
|
||||
protocol = proto.RuleProtocol_UDP
|
||||
case "all":
|
||||
protocol = proto.RuleProtocol_ALL
|
||||
default:
|
||||
protocol = proto.RuleProtocol_UNKNOWN
|
||||
}
|
||||
return &proto.ForwardingRule{
|
||||
Protocol: protocol,
|
||||
DestinationPort: f.DestinationPorts.ToProto(),
|
||||
TranslatedAddress: ipToBytes(f.TranslatedAddress),
|
||||
TranslatedPort: f.TranslatedPorts.ToProto(),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ForwardingRule) Equal(other *ForwardingRule) bool {
|
||||
return f.RuleProtocol == other.RuleProtocol &&
|
||||
f.DestinationPorts.Equal(&other.DestinationPorts) &&
|
||||
f.TranslatedAddress.Equal(other.TranslatedAddress) &&
|
||||
f.TranslatedPorts.Equal(&other.TranslatedPorts)
|
||||
}
|
||||
|
||||
func ipToBytes(ip net.IP) []byte {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
return ip4
|
||||
}
|
||||
return ip.To16()
|
||||
}
|
||||
|
||||
type Network struct {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/netbirdio/netbird/management/proto"
|
||||
)
|
||||
|
||||
// PolicyUpdateOperationType operation type
|
||||
type PolicyUpdateOperationType int
|
||||
|
||||
@@ -18,6 +22,21 @@ type RulePortRange struct {
|
||||
End uint16
|
||||
}
|
||||
|
||||
func (r *RulePortRange) ToProto() *proto.PortInfo {
|
||||
return &proto.PortInfo{
|
||||
PortSelection: &proto.PortInfo_Range_{
|
||||
Range: &proto.PortInfo_Range{
|
||||
Start: uint32(r.Start),
|
||||
End: uint32(r.End),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RulePortRange) Equal(other *RulePortRange) bool {
|
||||
return r.Start == other.Start && r.End == other.End
|
||||
}
|
||||
|
||||
// PolicyRule is the metadata of the policy
|
||||
type PolicyRule struct {
|
||||
// ID of the policy rule
|
||||
|
||||
@@ -30,3 +30,28 @@ type RouteFirewallRule struct {
|
||||
// isDynamic indicates whether the rule is for DNS routing
|
||||
IsDynamic bool
|
||||
}
|
||||
|
||||
func (r *RouteFirewallRule) Equal(other *RouteFirewallRule) bool {
|
||||
if r.Action != other.Action {
|
||||
return false
|
||||
}
|
||||
if r.Destination != other.Destination {
|
||||
return false
|
||||
}
|
||||
if r.Protocol != other.Protocol {
|
||||
return false
|
||||
}
|
||||
if r.Port != other.Port {
|
||||
return false
|
||||
}
|
||||
if !r.PortRange.Equal(&other.PortRange) {
|
||||
return false
|
||||
}
|
||||
if !r.Domains.Equal(other.Domains) {
|
||||
return false
|
||||
}
|
||||
if r.IsDynamic != other.IsDynamic {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -19,3 +19,34 @@ func Difference(a, b []string) []string {
|
||||
func ToPtr[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
|
||||
type comparableObject[T any] interface {
|
||||
Equal(other T) bool
|
||||
}
|
||||
|
||||
func MergeUnique[T comparableObject[T]](arr1, arr2 []T) []T {
|
||||
var result []T
|
||||
|
||||
for _, item := range arr1 {
|
||||
if !contains(result, item) {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range arr2 {
|
||||
if !contains(result, item) {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func contains[T comparableObject[T]](slice []T, element T) bool {
|
||||
for _, item := range slice {
|
||||
if item.Equal(element) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testObject struct {
|
||||
value int
|
||||
}
|
||||
|
||||
func (t testObject) Equal(other testObject) bool {
|
||||
return t.value == other.value
|
||||
}
|
||||
|
||||
func Test_MergeUniqueArraysWithoutDuplicates(t *testing.T) {
|
||||
arr1 := []testObject{{value: 1}, {value: 2}}
|
||||
arr2 := []testObject{{value: 2}, {value: 3}}
|
||||
result := MergeUnique(arr1, arr2)
|
||||
assert.Len(t, result, 3)
|
||||
assert.Contains(t, result, testObject{value: 1})
|
||||
assert.Contains(t, result, testObject{value: 2})
|
||||
assert.Contains(t, result, testObject{value: 3})
|
||||
}
|
||||
|
||||
func Test_MergeUniqueHandlesEmptyArrays(t *testing.T) {
|
||||
arr1 := []testObject{}
|
||||
arr2 := []testObject{}
|
||||
result := MergeUnique(arr1, arr2)
|
||||
assert.Empty(t, result)
|
||||
}
|
||||
|
||||
func Test_MergeUniqueHandlesOneEmptyArray(t *testing.T) {
|
||||
arr1 := []testObject{{value: 1}, {value: 2}}
|
||||
arr2 := []testObject{}
|
||||
result := MergeUnique(arr1, arr2)
|
||||
assert.Len(t, result, 2)
|
||||
assert.Contains(t, result, testObject{value: 1})
|
||||
assert.Contains(t, result, testObject{value: 2})
|
||||
}
|
||||
Reference in New Issue
Block a user