Skip to content

Network Access Control

Arrow implements automated VPN user access control to restrict device access to specific consultants. This ensures that only authorized personnel can connect to sensitive ARROW devices through the VPN, enhancing security and compliance.

The access control system operates at two levels:

  1. Organization-level defaults - Global setting applied to all devices unless overridden
  2. Device-level overrides - Per-device settings that take precedence over organization defaults

When consultants are assigned to devices in Arrow, the system automatically synchronizes these permissions to Netbird, creating appropriate groups and access policies.

The access control system resolves settings through a hierarchical inheritance model:

graph TD
    A[Organization] -->|has setting| B[user_access_control_default]
    C[Device Settings] -->|has setting| D[user_access_control]
    D -->|values| E{Setting Value}
    E -->|enabled| F[Access Control ON]
    E -->|disabled| G[Access Control OFF]
    E -->|inherit/empty| H[Use Org Default]

    F -->|creates| I[NetBird Group]
    I -->|members| J[Consultants Field]
    J -->|sync via hook| K[device_user_access.go]

    B -->|change triggers| L[org_access_control.go]
    L -->|syncs all devices| M[Inheriting Devices]

    D -->|change triggers| N[device_access_control.go]
    N -->|enables/disables| I

The DeviceAccessControlManager class (backend/api/netbird/device_groups.go) orchestrates all access control operations between Arrow and Netbird.

classDiagram
    class DeviceAccessControlManager {
        +IsAccessControlEnabled(deviceID) bool
        +GetOrgAccessControlDefault(orgID) bool
        +SyncDeviceUserAccess(deviceID, oldConsultants, newConsultants)
        +EnableAccessControlForDevice(deviceID)
        +DisableAccessControlForDevice(deviceID)
        +SyncAllOrgDevices(orgID)
    }

    class NetBirdClient {
        +GetUser(email) User
        +UpdateUser(userID, autoGroups)
        +CreateGroup(name) Group
        +GetGroup(id) Group
        +CreatePolicy(request) Policy
        +DeletePolicy(id)
    }

    class PocketBase {
        +device_requests
        +device_settings
        +system_integrations
    }

    DeviceAccessControlManager --> NetBirdClient : uses
    DeviceAccessControlManager --> PocketBase : reads/writes
MethodDescription
IsAccessControlEnabledResolves effective access control state by checking device override, then falling back to organization default
GetOrgAccessControlDefaultRetrieves the organization-level default from system_integrations.user_access_control_default
SyncDeviceUserAccessSynchronizes consultant assignments to Netbird groups when the consultants field changes
EnableAccessControlForDeviceCreates device group, consultant group, and one-way access policy
DisableAccessControlForDeviceRemoves access restrictions, allowing all users to access the device
SyncAllOrgDevicesUpdates all devices inheriting organization default when the default changes

Arrow creates two types of groups for each device with access control enabled:

Group TypeNaming PatternContents
Device Groupdevice-{identifier}Contains only the device peer
Consultant Groupconsultants-{identifier}Contains Netbird users (not peers) assigned to the device

Access control policies are configured as one-way (bidirectional=false):

  • Consultants can connect TO devices - Allows SSH, VNC, and other management access
  • Devices cannot connect back to consultants - Prevents lateral movement from compromised devices

This model ensures that even if a device is compromised, it cannot be used to attack consultant workstations.

Arrow uses PocketBase hooks to automatically enforce access control when data changes. Three hooks work together to maintain synchronization:

File: backend/hooks/device_user_access.go

Trigger: Updates to the consultants field in device_requests collection

Behavior:

  1. Detects changes to the consultants array
  2. Checks if access control is enabled for the device
  3. Calls SyncDeviceUserAccess to update Netbird user auto_groups
  4. Only syncs if access control is enabled
sequenceDiagram
    participant User
    participant Arrow
    participant Hook
    participant Manager
    participant Netbird

    User->>Arrow: Update consultants on device_request
    Arrow->>Hook: Trigger device_user_access hook
    Hook->>Manager: Check IsAccessControlEnabled()
    Manager-->>Hook: true
    Hook->>Manager: SyncDeviceUserAccess(old, new)
    Manager->>Manager: EnsureDeviceGroup()
    Manager->>Manager: EnsureConsultantGroup()
    Manager->>Netbird: Get NetBird user by email
    Netbird-->>Manager: User object
    Manager->>Netbird: Update user auto_groups
    Netbird-->>Manager: Success
    Manager->>Manager: EnsureOneWayAccessPolicy()
    Manager->>Netbird: Create/Update policy
    Netbird-->>Manager: Policy created
    Manager-->>Hook: Sync complete
    Hook-->>Arrow: Success

File: backend/hooks/device_access_control.go

Trigger: Updates to the user_access_control field in device_settings collection

Behavior:

  1. Detects changes to access control setting (enabled/disabled/inherit)
  2. Resolves effective state based on device setting and organization default
  3. Calls EnableAccessControlForDevice or DisableAccessControlForDevice as appropriate
  4. Creates or removes groups and policies accordingly

Setting Values:

  • enabled - Access control is explicitly enabled for this device
  • disabled - Access control is explicitly disabled for this device
  • inherit - Use organization default setting

File: backend/hooks/org_access_control.go

Trigger: Updates to user_access_control_default field in system_integrations collection (type=“netbird”)

Behavior:

  1. Detects changes to organization default setting
  2. Calls SyncAllOrgDevices to update all devices
  3. Skips devices with explicit overrides (enabled/disabled)
  4. Only updates devices set to inherit

Creates a device group if it doesn’t exist:

func (m *DeviceAccessControlManager) EnsureDeviceGroup(deviceIdentifier string) (*Group, error)
  • Group name: device-{identifier}
  • Contains: Only the device peer
  • Created: When access control is first enabled for a device

Creates a consultant group for user assignments:

func (m *DeviceAccessControlManager) EnsureConsultantGroup(deviceIdentifier string) (*Group, error)
  • Group name: consultants-{identifier}
  • Contains: Netbird users assigned to the device
  • Updated: When consultants are added/removed from the device

Creates or updates the access policy:

func (m *DeviceAccessControlManager) EnsureOneWayAccessPolicy(deviceIdentifier string) (*Policy, error)

Policy Configuration:

PropertyValue
Nameaccess-{identifier}
SourceConsultant group
DestinationDevice group
Bidirectionalfalse (one-way only)
ProtocolAll
PortsAll

Removes groups and policies when a device is deleted:

func (m *DeviceAccessControlManager) CleanupDeviceGroup(deviceIdentifier string) error
  • Deletes the device group
  • Deletes the consultant group
  • Removes the access policy
  • Updates user auto_groups to remove references

Stored in system_integrations collection where type = "netbird":

{
"type": "netbird",
"user_access_control_default": true
}
ValueBehavior
trueAccess control enabled by default for all devices
falseAccess control disabled by default

Stored in device_settings collection:

{
"device": "device_id",
"user_access_control": "enabled",
"netbird_group_id": "group-abc123",
"netbird_consultant_group_id": "group-def456"
}
SettingBehavior
enabledAccess control explicitly enabled
disabledAccess control explicitly disabled
inheritUse organization default

When determining if access control is enabled for a device:

  1. Check device_settings.user_access_control
  2. If enabled or disabled, use that value
  3. If inherit (or not set), check system_integrations.user_access_control_default
  4. Default to false if no setting found

The device_settings collection stores Netbird group IDs for reference:

FieldDescription
netbird_group_idID of the device group in Netbird
netbird_consultant_group_idID of the consultant group in Netbird

These IDs are used for efficient lookups and cleanup operations.

Enable Access Control at Organization Level

Section titled “Enable Access Control at Organization Level”

For security, enable access control at the organization level:

  1. Navigate to VPN settings in Arrow
  2. Enable “User Access Control Default”
  3. All devices will inherit this setting unless explicitly overridden

Use device-level overrides in specific scenarios:

ScenarioRecommended Setting
Shared lab devicesdisabled - Allow all consultants
Testing/development devicesdisabled - Allow team access
High-security devicesenabled - Restrict to specific consultants
Client-sensitive devicesenabled - Limit access to assigned team

For access control to work correctly:

  • Consultants must have Netbird user accounts
  • Email addresses must match between Arrow and Netbird
  • Users are matched by email during sync operations

When making bulk changes (e.g., enabling access control for many devices):

  • Certificate generation has a 30-second delay between operations
  • This prevents Let’s Encrypt rate limit errors
  • Bulk operations may take several minutes to complete
FilePurpose
backend/api/netbird/device_groups.goDeviceAccessControlManager implementation
backend/api/netbird/client.goNetBirdClient API wrapper
backend/hooks/device_user_access.goConsultant sync hook
backend/hooks/device_access_control.goDevice-level toggle hook
backend/hooks/org_access_control.goOrganization-level toggle hook
CollectionRelevant Fields
device_requestsconsultants - Array of assigned consultant IDs
device_settingsuser_access_control, netbird_group_id, netbird_consultant_group_id
system_integrationstype, user_access_control_default, management_url, api_key

Virtual machines provisioned through ARROW have specialized access control handling:

When VMs are provisioned with access control enabled:

  1. Device Group: device-{vm-identifier} created containing only the VM peer
  2. Consultant Group: consultants-{vm-identifier} created for assigned users
  3. Access Policy: One-way policy allowing consultants to reach the VM

VM access policies follow the same one-way model as physical devices:

PropertyValue
SourceConsultant group
DestinationVM device group
Bidirectionalfalse
ProtocolAll

This prevents compromised VMs from being used to attack consultant workstations.

When VMs are completed, access control resources are automatically cleaned up:

flowchart TD
    A[VM Completion Triggered] --> B[Get Device Settings]
    B --> C[Retrieve NetBird Client]
    C --> D[Delete VM Peer]
    D --> E[Delete Device Group]
    E --> F[Delete Consultant Group]
    F --> G[Remove Access Policy]
    G --> H[Clear Group IDs from Settings]
    H --> I[Cleanup Complete]

The completion process removes:

  • NetBird Peer: The VM’s VPN peer registration
  • Device Group: The device-{id} group
  • Consultant Group: The consultants-{id} group
  • Access Policy: The access-{id} policy
  • User References: Consultant auto_group assignments