Firewall System

Complete guide to the two-tier firewall system - department and VM-level rules, nwfilter XML generation, inheritance, and templates

Firewall System

Introduction

Infinibay implements a sophisticated two-tier firewall system that provides network security for virtual machines through hierarchical rule inheritance. The system operates at both the department and VM levels, allowing organizations to define baseline security policies at the department level while enabling VM-specific customizations. Deeply integrated with libvirt's nwfilter subsystem, the firewall automatically synchronizes rules to running VMs, supports pre-configured templates for common use cases, validates rules for conflicts, and maintains consistency between the database and libvirt state.

Architecture Overview

Two-Tier Hierarchy

The firewall system implements a hierarchical rule structure with two distinct levels:

  • Department Level: Base firewall rules applied to all VMs within a department. These rules establish organization-wide security policies and provide a consistent security baseline.
  • VM Level: VM-specific rules that automatically inherit from their department and can selectively override department rules when necessary. This provides flexibility for exceptions while maintaining centralized control.

Design Patterns

The architecture follows established design patterns to manage complexity and ensure maintainability:

  • Facade Pattern: FirewallManager (from services/firewall/FirewallManager.ts) provides a simplified interface that coordinates complex multi-step operations across multiple services, hiding implementation details from callers.
  • Strategy Pattern: DepartmentFilterStrategy vs VMFilterStrategy (from services/firewall/strategies/) implement different filter creation logic while sharing a common interface, allowing the system to treat both entity types polymorphically.
  • Factory Pattern: FirewallFilterFactory (from services/firewall/FirewallFilterFactory.ts) encapsulates the logic for creating appropriate strategy instances based on entity type, providing a clean abstraction for filter creation.

Component Layers

The system is organized into distinct layers, each with specific responsibilities:

  • Facade Layer: FirewallManager simplifies complex operations like VM firewall initialization, providing clean APIs for common workflows.
  • Factory Layer: FirewallFilterFactory creates appropriate strategy instances and provides facade methods for common operations.
  • Strategy Layer: DepartmentFilterStrategy and VMFilterStrategy implement entity-specific filter creation logic, handling differences in XML generation and inheritance.
  • Service Layer:
    • FirewallRuleService: Database CRUD operations for rules and rule sets
    • NWFilterXMLGeneratorService: Converts database rules to libvirt nwfilter XML
    • LibvirtNWFilterService: Interfaces with libvirt nwfilter subsystem
    • FirewallValidationService: Validates rules for conflicts and correctness
  • Data Layer: Prisma models (FirewallRuleSet, FirewallRule) with foreign key relationships to departments and VMs.

FirewallManager - Facade Component

Purpose

The FirewallManager (from FirewallManager.ts lines 70-117) serves as the primary entry point for firewall operations, providing a high-level API that:

  • Simplifies complex firewall initialization during VM creation by coordinating multiple services
  • Handles edge cases like broken foreign keys and missing parent filters
  • Provides clean methods for common workflows (creation, resynchronization, filter name retrieval)
  • Ensures consistency between database state and libvirt state

Key Methods

ensureFirewallInfrastructure(entityType, entityId, description) (lines 151-267)

Creates the basic firewall infrastructure for a new entity (department or VM). This method:

  • Creates or finds a FirewallRuleSet in the database using FirewallRuleService.createRuleSet
  • Links the rule set to the entity via foreign key relationship
  • Creates an empty nwfilter in libvirt via FirewallFilterFactory
  • Implements self-healing logic to detect and repair broken foreign key relationships (lines 211-236)
  • Returns the created/found FirewallRuleSet

This method is typically called by Prisma lifecycle callbacks (afterCreateDepartment, afterCreateMachine) to proactively create infrastructure when entities are created.

ensureFirewallForVM(vmId, departmentId) (lines 314-487)

Performs complete firewall setup for a VM with comprehensive verification and fallback logic. This method:

  • Validates that the VM exists and belongs to the specified department
  • Ensures the department filter exists in libvirt (creates if missing, lines 365-396)
  • Ensures the VM filter exists in libvirt with proper inheritance (creates if missing, lines 401-432)
  • Retrieves filter names for use in VM domain XML
  • Implements self-healing for missing department filters (lines 454-476)
  • Returns filter names and creation status

This method serves as a verification layer during VM creation and provides fallback recovery if Prisma callbacks failed.

resyncVMFirewall(vmId) (lines 515-606)

Re-synchronizes firewall state for an existing VM by regenerating and redefining filters in libvirt. This method:

  • Queries the VM with all related firewall rules and department information
  • Recreates the department filter from database rules
  • Recreates the VM filter with proper inheritance
  • Updates FirewallRuleSet.lastSyncedAt timestamps
  • Useful for troubleshooting or recovering from manual libvirt changes

getFilterNames(vmId) (lines 625-656)

Retrieves filter names for a VM without creating any resources. This method:

  • Queries the VM with department relation
  • Generates filter names using FilterNameGenerator
  • Returns both department and VM filter names
  • Used when filter names are needed for XML generation but creation should not occur

Self-Healing Logic

The FirewallManager implements sophisticated self-healing mechanisms (lines 211-236, 365-396, 401-432):

  • Broken Foreign Keys: Detects when a FirewallRuleSet exists but isn't linked to its entity via foreign key. Automatically repairs the relationship by updating the entity record.
  • Missing Department Filters: During VM filter creation, verifies the department filter exists in libvirt. If missing, creates it automatically before proceeding.
  • Race Conditions: Works with FirewallRuleService upsert logic to handle concurrent rule set creation from Prisma callbacks.

Integration with Prisma Callbacks

The FirewallManager is called by Prisma lifecycle callbacks:

  • afterCreateDepartment: Calls ensureFirewallInfrastructure to create department firewall infrastructure
  • afterCreateMachine: Calls ensureFirewallInfrastructure to create VM firewall infrastructure

Additionally, ensureFirewallForVM serves as a verification layer during VM creation (called from CreateMachineService) and provides fallback recovery if callbacks failed or state became inconsistent.

FirewallFilterFactory - Strategy Factory

Purpose

The FirewallFilterFactory (from FirewallFilterFactory.ts lines 11-49) encapsulates the complexity of choosing and creating the correct filter strategy for a given entity type. It:

  • Creates strategy instances with proper service dependencies injected
  • Provides facade methods for common operations like filter creation and name generation
  • Centralizes service instantiation to ensure consistent dependencies across strategies
  • Abstracts away the differences between department and VM filter creation

Strategy Selection

The factory implements strategy selection logic (lines 94-109) based on entity type:

  • RuleSetType.DEPARTMENT → Creates DepartmentFilterStrategy instance
  • RuleSetType.VM → Creates VMFilterStrategy instance
  • Unknown entity types → Throws descriptive error

Each strategy receives the same set of service dependencies, ensuring consistent behavior across entity types.

Key Methods

createFilter(entityType, entityId, rules) (lines 94-109)

Delegates filter creation to the appropriate strategy based on entity type. This method:

  • Selects the correct strategy using internal logic
  • Passes the entityId and rules array to the strategy's createFilter method
  • Returns the strategy's creation result (filter name, UUID, success status)

getFilterName(entityType, entityId) (lines 121-123)

Generates the filter name for an entity using FilterNameGenerator. This method:

  • Takes entity type and ID as parameters
  • Returns the deterministic filter name (e.g., ibay-vm-a1b2c3d4)
  • Used when the name is needed without creating a filter

ensureDepartmentFilter(departmentId) (lines 137-162)

Queries the department with its firewall rules and creates/updates the filter in libvirt. This method:

  • Queries the department with all related FirewallRule records
  • Validates that the department has a FirewallRuleSet
  • Calls DepartmentFilterStrategy.createFilter with the department's rules
  • Returns the creation result with filter details

ensureVMFilter(vmId) (lines 177-203)

Queries the VM with its department and firewall rules, then creates/updates the filter with proper inheritance. This method:

  • Queries the VM with department relation and all related FirewallRule records
  • Validates that the VM has a department assigned
  • Validates that the VM has a FirewallRuleSet
  • Calls VMFilterStrategy.createFilter with the VM's rules and department information
  • Returns the creation result including inheritance information

Dependency Injection

The factory constructor (lines 57-80) instantiates all required services and injects them into strategy constructors:

  • NWFilterXMLGeneratorService: XML generation from rules
  • LibvirtNWFilterService: Libvirt nwfilter operations
  • FirewallValidationService: Rule conflict and validation checks
  • PrismaClient: Database access for strategy queries

This ensures that all strategies use the same service instances and provides a single point of configuration for service dependencies.

Filter Strategies

DepartmentFilterStrategy

Purpose (from strategies/DepartmentFilterStrategy.ts lines 10-26)

Creates base filters that serve as the foundation for VM inheritance. Department filters establish organization-wide security policies and have no parent filter reference.

Workflow (lines 46-81)

  1. Validate Rules: Call FirewallValidationService.validateRuleConflicts to check for conflicts. Empty arrays pass validation and are common for new departments.
  2. Generate Filter Name: Use FilterNameGenerator to create deterministic name (e.g., ibay-department-a1b2c3d4).
  3. Get Existing UUID: Query libvirt for existing filter UUID to support redefinition (updating existing filters).
  4. Generate XML: Call NWFilterXMLGeneratorService.generateFilterXML without parent filter reference.
  5. Define in Libvirt: Call LibvirtNWFilterService.defineFilter to create/update the filter.
  6. Return Result: Return creation result with filter name, UUID, and success status.

Empty Filters (lines 47-50)

Empty department filters (no rules) are valid and common for new departments. They serve as a base for VM inheritance even without rules, allowing VMs to inherit the department context while defining their own rules.

No Parent Filter

Department filters are base filters with no <filterref> element in their XML. They are not derived from any other filter and represent the top of the inheritance hierarchy.

VMFilterStrategy

Purpose (from strategies/VMFilterStrategy.ts lines 11-30)

Creates VM filters that automatically inherit from their department's filter. VM filters can add additional rules or override department rules using explicit flags.

Inheritance Mechanism (lines 17-21)

Uses libvirt's <filterref> XML element to reference the department filter by name. This creates a parent-child relationship where:

  • Libvirt automatically evaluates department rules first
  • VM rules are evaluated second
  • Changes to the department filter automatically affect all VMs in that department

Workflow (lines 52-112)

  1. Query VM: Retrieve VM with department relation from database.
  2. Ensure Department Filter Exists: Verify department filter exists in libvirt (lines 72-81). Throws descriptive error if missing.
  3. Validate VM Rules: Call FirewallValidationService.validateRuleConflicts to check for conflicts.
  4. Generate VM Filter Name: Use FilterNameGenerator to create name (e.g., ibay-vm-e5f6g7h8).
  5. Get Existing UUID: Query libvirt for existing filter UUID to support redefinition.
  6. Generate XML with Parent: Call NWFilterXMLGeneratorService.generateFilterXML with parentFilterName parameter to create inheritance.
  7. Define in Libvirt: Call LibvirtNWFilterService.defineFilter to create/update the filter.
  8. Return Result: Return creation result with filter details.

Department Filter Validation (lines 72-81)

The strategy validates that the department filter exists in libvirt before creating the VM filter. If missing, it throws a descriptive error with the department ID and suggests creating the department filter first. This prevents creating VM filters with broken inheritance references.

Empty VM Filters (lines 67-70)

Empty VM filters (no VM-specific rules) are valid and common. They inherit all department rules via the <filterref> element without adding additional VM-specific rules.

FirewallRuleService - Data Access Layer

Purpose

The FirewallRuleService (from FirewallRuleService.ts lines 51-53) provides database access methods for firewall rules and rule sets, abstracting Prisma operations and providing clean CRUD APIs for other services.

Key Methods

createRuleSet(entityType, entityId, name, internalName, priority) (lines 87-117)

Creates or finds a FirewallRuleSet using Prisma's upsert operation. This method:

  • Uses internalName as the unique identifier for upsert
  • Creates new record if not found, returns existing if found
  • Handles race conditions where Prisma callbacks create the ruleset before this query sees it (lines 94-113)
  • Returns the created or found FirewallRuleSet

Important: This method does NOT link the FirewallRuleSet to the entity via foreign key. The caller is responsible for updating the entity's firewallRuleSetId field (lines 64-85).

createRule(ruleSetId, ruleData) (lines 122-132)

Creates a new FirewallRule associated with a rule set. This method:

  • Takes rule set ID and rule configuration data
  • Creates the rule in the database
  • Returns the created FirewallRule

updateRule(ruleId, ruleData) (lines 137-145)

Updates an existing firewall rule. This method:

  • Takes rule ID and partial rule data (fields to update)
  • Updates the rule in the database
  • Returns the updated FirewallRule

deleteRule(ruleId) (lines 150-156)

Deletes a firewall rule by ID. This method:

  • Takes rule ID
  • Deletes the rule from the database
  • Returns the deleted FirewallRule

getRulesByEntity(entityType, entityId) (lines 161-173)

Retrieves all firewall rules for a specific entity. This method:

  • Queries FirewallRuleSet with rules included
  • Filters by entity type and ID
  • Returns array of FirewallRule objects or null if rule set not found

getRuleSetByEntity(entityType, entityId) (lines 178-193)

Retrieves the FirewallRuleSet for a specific entity with all associated rules. This method:

  • Queries by entity type and ID
  • Includes all related FirewallRule records
  • Returns FirewallRuleSet with rules or null if not found

updateRuleSetSyncStatus(ruleSetId, libvirtUuid, xmlContent) (lines 210-225)

Updates the synchronization status of a rule set after successfully applying it to libvirt. This method:

  • Updates libvirtUuid field with the filter UUID from libvirt
  • Updates xmlContent field with the generated XML
  • Updates lastSyncedAt timestamp to current time
  • Used to track when rules were last synchronized with libvirt

Upsert Pattern

The createRuleSet method uses Prisma's upsert operation (lines 94-113) to handle race conditions. When Prisma lifecycle callbacks create a FirewallRuleSet concurrently, the upsert ensures:

  • If the ruleset doesn't exist, it's created
  • If the ruleset exists (created by callback), it's returned
  • No duplicate rulesets are created
  • No errors are thrown for concurrent creation attempts

Foreign Key Responsibility

The service does NOT automatically link the FirewallRuleSet to the entity (lines 64-85). The caller must:

  1. Call createRuleSet to create the ruleset
  2. Update the entity record with firewallRuleSetId foreign key
  3. Handle any race conditions or existing relationships

This separation of concerns allows callers to implement custom linking logic and handle edge cases like orphaned rulesets.

NWFilterXMLGeneratorService - XML Generation

Purpose

The NWFilterXMLGeneratorService (from NWFilterXMLGeneratorService.ts lines 14-16) converts database firewall rules into libvirt nwfilter XML format, handling all XML structure details and rule element generation.

Filter Naming

The service delegates filter name generation to FilterNameGenerator (lines 25-27), ensuring consistent naming across the system. This centralized naming logic produces deterministic names like ibay-department-a1b2c3d4 and ibay-vm-e5f6g7h8.

XML Generation

generateFilterXML(filterName, rules, existingUuid?, parentFilterName?) (lines 35-71)

Generates complete nwfilter XML from an array of firewall rules. This method:

  1. Sort Rules by Priority: Lower priority numbers are evaluated first (higher priority).
  2. Create Filter Object: Builds the root filter structure with:
    • $: Attributes object with name and chain='root'
    • uuid: Uses existing UUID for redefinition or generates new one
  3. Add Parent Filter Reference: If parentFilterName is provided (for VM filters), adds <filterref filter="parent-name"/> element (lines 49-58).
  4. Convert Rules to Elements: Calls convertRuleToXMLElement for each rule to generate rule elements.
  5. Build XML: Uses xml2js.Builder to convert JavaScript object to XML string.

Rule Element Generation

convertRuleToXMLElement(rule) (lines 76-135)

Converts a single FirewallRule database record to an nwfilter rule XML element. This method:

  1. Map Action: Converts database enum to lowercase (ACCEPT → accept, DROP → drop, REJECT → reject).
  2. Map Direction: Converts database enum (IN → in, OUT → out, INOUT → inout).
  3. Add Connection State: If rule.connectionState JSON contains states (NEW, ESTABLISHED, RELATED, INVALID), adds statematch attribute.
  4. Create Attributes Object: Builds rule attributes including action, direction, priority, connection state.
  5. Add Protocol-Specific Attributes: Based on rule.protocol, adds:
    • Source/destination port ranges (for tcp, udp)
    • Source/destination IP addresses with CIDR masks
    • Protocol-specific attributes (ICMP type/code, etc.)
  6. Create Protocol Element: Creates the protocol-specific element (tcp, udp, icmp, etc.) with nested attributes.

Parent Filter Reference

For VM filters, the parentFilterName parameter (lines 49-58) adds a <filterref> element to the filter XML:

<filterref filter="ibay-department-a1b2c3d4"/>

This instructs libvirt to evaluate the department filter's rules before evaluating the VM filter's rules, implementing the inheritance mechanism.

UUID Handling

The service supports filter redefinition by accepting an existingUuid parameter (line 45). When provided:

  • Uses the existing UUID for the filter
  • Libvirt updates the existing filter in-place
  • Maintains consistency with database FirewallRuleSet.libvirtUuid

When not provided, libvirt generates a new UUID and returns it from the defineFilter operation.

LibvirtNWFilterService - Libvirt Integration

Purpose

The LibvirtNWFilterService (from LibvirtNWFilterService.ts lines 8-10) provides a clean interface to libvirt's nwfilter subsystem, handling all low-level libvirt API calls and error handling.

Key Methods

defineFilter(xml) (lines 19-37)

Defines a new nwfilter or redefines an existing one from XML. This method:

  • Validates that the libvirt connection is alive (lines 20-22)
  • Calls libvirtConn.defineNWFilter(xml) to create/update the filter
  • Extracts and returns the filter UUID from the result
  • Throws descriptive errors if the operation fails

undefineFilter(name) (lines 42-51)

Removes a filter from libvirt by name. This method:

  • Calls libvirtConn.undefineNWFilter(name)
  • Removes the filter from libvirt's configuration
  • Does not affect VMs currently using the filter (they retain cached rules)

listAllInfinibayFilters() (lines 56-66)

Lists all filters with the 'ibay-' prefix. This method:

  • Calls libvirtConn.listNWFilters()
  • Filters the result to only include filters with 'ibay-' prefix (line 65)
  • Returns array of filter name strings
  • Useful for cleanup and auditing operations

cleanupAllInfinibayFilters() (lines 72-87)

Removes all Infinibay filters from libvirt. This method:

  • Calls listAllInfinibayFilters to get all Infinibay filters
  • Iterates through filters and calls undefineFilter for each
  • Catches and logs errors for individual filter removal failures
  • Useful for system cleanup or testing

getFilterXML(filterName) (lines 92-100)

Retrieves the XML definition of a filter by name. This method:

  • Calls libvirtConn.getNWFilterXML(filterName)
  • Returns the XML string
  • Useful for debugging and verification

filterExists(filterName) (lines 111-120)

Checks if a filter exists in libvirt by name. This method:

  • Calls libvirtConn.lookupNWFilterByName(filterName) (lines 115-119)
  • Returns true if the lookup succeeds
  • Returns false if the lookup throws an error (filter not found)
  • Gracefully handles lookup failures

getFilterUuid(filterName) (lines 128-146)

Retrieves the UUID of an existing filter by name. This method:

  • Calls libvirtConn.lookupNWFilterByName(filterName)
  • Extracts the UUID from the result object
  • Returns the UUID string or null if not found (lines 142-145)
  • Used to support filter redefinition with existing UUIDs

Connection Validation

All methods that interact with libvirt validate the connection is alive (lines 20-22) before attempting operations. This prevents errors from closed or invalid connections and provides clearer error messages.

Error Handling

The service implements graceful error handling:

  • filterExists returns false instead of throwing errors (lines 115-119)
  • getFilterUuid returns null instead of throwing errors (lines 142-145)
  • Lookup failures are caught and handled appropriately based on method semantics

Naming Convention

The service uses the 'ibay-' prefix for all Infinibay filters (line 65). This:

  • Allows easy identification of Infinibay-managed filters
  • Enables bulk operations like listAllInfinibayFilters
  • Prevents accidental modification of system or other application filters
  • Supports cleanup operations via prefix filtering

FirewallValidationService - Rule Validation

Purpose

The FirewallValidationService (from FirewallValidationService.ts lines 30-32) provides comprehensive validation of firewall rules before they are applied to libvirt, preventing configuration errors and detecting potential conflicts.

Validation Types

Conflict Detection (lines 37-67)

The validateRuleConflicts method detects problematic rule combinations:

  • Contradictory Rules: Same traffic pattern (protocol, ports, IPs) with different actions (ACCEPT vs DROP)
  • Port Overlaps: Rules with overlapping port ranges that may cause confusion
  • Duplicates: Identical rule configurations that serve no purpose
  • Priority Conflicts: Same priority with conflicting actions

Override Validation (lines 72-88)

The validateOverrideRules method ensures VM override rules are valid:

  • Verifies that VM rules with overridesDept=true actually target department rules
  • Checks that a matching department rule exists with the same traffic pattern
  • Prevents nonsensical overrides of non-existent department rules
  • Returns validation errors if override rules don't match department rules

Priority Order Validation (lines 93-116)

The validatePriorityOrder method checks for priority conflicts:

  • Detects rules with identical priority but conflicting actions
  • Ensures evaluation order is deterministic
  • Flags potential rule ordering issues

Input Validation (lines 342-364)

The validateRuleInputs method validates individual rule parameters:

  • Port ranges (1-65535, start ≤ end)
  • IP addresses (IPv4 and IPv6 with CIDR masks)
  • Protocol constraints (ports only for port-supporting protocols)
  • Required field presence

Conflict Types

The service defines conflict types as an enum (lines 4-10):

  • DUPLICATE: Identical rule configuration (same protocol, ports, IPs, action)
  • CONTRADICTORY: Same traffic pattern but different actions (e.g., one ACCEPT, one DROP)
  • PORT_OVERLAP: Overlapping port ranges that may cause confusion
  • PRIORITY_CONFLICT: Same priority with conflicting actions

Port Validation

validatePortRange (lines 369-407)

Validates port number ranges:

  • Port numbers must be 1-65535
  • Start port must be ≤ end port
  • End port requires start port to be specified
  • Both ports must be within valid range
  • Returns array of validation error messages

validatePortOverlap (lines 148-177, 192-248)

Detects overlapping port ranges between rules:

  • Compares source and destination port ranges
  • Detects full overlaps and partial overlaps
  • Provides detailed overlap information (which ports overlap)
  • Generates helpful suggestions based on rule actions
  • Formats error messages with affected port ranges

IP Validation

validateIpAddress (lines 412-446)

Validates IP addresses and CIDR masks using Node.js net module:

  • Supports both IPv4 and IPv6 addresses
  • Validates IP format using net.isIP and net.isIPv4/isIPv6
  • Validates CIDR mask range (0-32 for IPv4, 0-128 for IPv6)
  • Ensures mask is only specified with valid IP address
  • Returns array of validation error messages

Protocol Constraints

validateProtocolConstraints (lines 451-463)

Validates protocol-specific constraints:

  • Identifies portless protocols: icmp, icmpv6, igmp, ah, esp, all
  • Warns if port specifications are provided for portless protocols
  • Prevents configuration errors where ports are specified but ignored
  • Returns array of warning messages

Overlap Detection

The service implements sophisticated overlap detection (lines 148-177, 192-248):

  1. Range Comparison: Compares source and destination port ranges between rules
  2. Overlap Calculation: Determines if ranges overlap (start1 ≤ end2 && start2 ≤ end1)
  3. Overlap Details: Calculates which specific ports overlap
  4. Action-Based Suggestions: Provides different suggestions based on whether rules have same or different actions
  5. Message Formatting: Creates detailed, user-friendly error messages

For example, if one rule allows ports 80-100 and another allows ports 90-110:

  • Detects overlap on ports 90-100
  • Suggests consolidating rules if actions match
  • Warns about contradictory behavior if actions differ

Rule Types and Configuration

Rule Actions

From the Prisma schema (lines 685-689), the system supports three actions:

  • ACCEPT: Allow traffic matching the rule. Packets are permitted to pass through the firewall.
  • DROP: Silently drop traffic without sending any response to the sender. Connection appears to time out.
  • REJECT: Drop traffic but send a rejection response (ICMP unreachable or TCP reset) to the sender.

Rule Directions

From the Prisma schema (lines 691-695), rules can filter traffic in three directions:

  • IN: Incoming traffic to the VM (ingress). Rules apply to packets entering the VM's network interface.
  • OUT: Outgoing traffic from the VM (egress). Rules apply to packets leaving the VM's network interface.
  • INOUT: Bidirectional traffic (both incoming and outgoing). Rules apply to packets in both directions.

Rule Parameters

From the FirewallRule Prisma model (lines 654-678), each rule supports extensive filtering parameters:

Basic Parameters:

  • name: Human-readable rule name
  • description: Detailed explanation of the rule's purpose
  • action: ACCEPT, DROP, or REJECT
  • direction: IN, OUT, or INOUT
  • priority: Evaluation priority (0-1000, lower = higher priority)

Protocol Filtering:

  • protocol: Protocol to filter (tcp, udp, icmp, icmpv6, igmp, ah, esp, all, etc.)

Port Filtering:

  • srcPortStart: Source port range start (1-65535)
  • srcPortEnd: Source port range end (1-65535)
  • dstPortStart: Destination port range start (1-65535)
  • dstPortEnd: Destination port range end (1-65535)

IP Address Filtering:

  • srcIpAddr: Source IP address (IPv4 or IPv6)
  • srcIpMask: Source IP CIDR mask (0-32 for IPv4, 0-128 for IPv6)
  • dstIpAddr: Destination IP address (IPv4 or IPv6)
  • dstIpMask: Destination IP CIDR mask (0-32 for IPv4, 0-128 for IPv6)

Connection State Tracking:

  • connectionState: JSON field containing array of states (NEW, ESTABLISHED, RELATED, INVALID)

Override Flag:

  • overridesDept: Boolean indicating if this VM rule explicitly overrides a department rule

Priority System

The system uses a priority-based evaluation model:

  • Lower number = higher priority (evaluated first)
  • Department filters: Base priority 1000 (lower priority)
  • VM filters: Base priority 500 (higher priority, evaluated after department rules)
  • Individual rules: Priority 0-1000 (configurable per rule)
  • Evaluation order: Rules are sorted by priority before XML generation

This allows fine-grained control over rule evaluation order. For example:

  • Department rule priority 500: Allow HTTPS (443)
  • VM rule priority 400: Block HTTPS from specific IP range
  • The VM rule is evaluated first due to higher priority (lower number)

Firewall Templates

Overview

From frontend/src/config/firewallTemplates.ts (lines 1-10), firewall templates are:

  • Frontend-only abstractions - Not stored in the backend database
  • Pre-configured rule bundles - Simplify common configuration scenarios
  • 2-click setup - Align with Infinibay's philosophy of simplicity for business owners
  • Expandable to rules - Convert to individual FirewallRule objects when applied

Templates provide a user-friendly way to configure firewalls without requiring deep networking knowledge. Instead of manually creating rules for a web server, users select the "Web Server" template and get a complete, validated configuration.

Template Structure

From lines 14-24, each template contains:

  • id: Unique identifier (string)
  • name: Internal name (kebab-case)
  • displayName: User-facing name
  • description: Explanation of template purpose
  • icon: Icon identifier for UI
  • category: Template category (server, desktop, development, database)
  • servicePresets: Array of service preset IDs (references to servicePresets.ts)
  • customRules: Additional rules not covered by service presets
  • priority: Base priority for all template rules

Available Templates

From lines 45-241, Infinibay provides several pre-configured templates:

Web Server Template (lines 177-218)

  • Purpose: Public-facing web server
  • Allowed Inbound: HTTP (80), HTTPS (443), SSH (22)
  • Blocked Inbound: MySQL, PostgreSQL, Redis, MongoDB
  • Allowed Outbound: All (for package updates, API calls)
  • Use Case: WordPress, web applications, static sites

Web Server Secure Template (lines 219-240)

  • Purpose: Security-focused web server
  • Allowed Inbound: HTTPS (443) only, SSH (22)
  • Blocked Inbound: HTTP (80), databases
  • Use Case: Production web servers requiring TLS

Database Server Template (lines 47-78)

  • Purpose: Dedicated database server
  • Allowed Inbound: MySQL (3306), PostgreSQL (5432), MongoDB (27017), Redis (6379), SSH (22)
  • Blocked Inbound: HTTP (80), HTTPS (443)
  • Allowed Outbound: Limited (databases typically don't need internet access)
  • Use Case: Backend database servers isolated from public internet

Desktop Basic Template (lines 81-100)

  • Purpose: Standard user desktop/workstation
  • Allowed Inbound: RDP (3389), SSH (22)
  • Allowed Outbound: HTTP, HTTPS, DNS, email (SMTP, POP3, IMAP), file sharing (SMB, NFS)
  • Use Case: Employee workstations, general-purpose VMs

Desktop Secure Template (lines 101-132)

  • Purpose: Security-focused desktop for sensitive work
  • Allowed Inbound: RDP (3389), SSH (22)
  • Allowed Outbound: HTTPS (443), DNS (53) only - blocks HTTP (80) and file sharing
  • Use Case: Finance department, executives, environments requiring strict outbound control

Development Template (lines 135-174)

  • Purpose: Developer workstation
  • Allowed Inbound: SSH (22), dev ports (8000-9000 range)
  • Allowed Outbound: All protocols, databases, web, file sharing
  • Use Case: Software development, testing environments

Template Expansion

expandTemplateToRules(template) (lines 291-319)

Converts a template definition to an array of individual firewall rules ready for the backend API. This function:

  1. Expands Service Presets: Looks up each preset ID in servicePresets configuration and extracts its rules
  2. Combines with Custom Rules: Merges service preset rules with template's custom rules
  3. Applies Template Priority: Sets the template's base priority on all rules
  4. Returns Rule Array: Returns array of TemplateRule objects with all necessary fields

The expansion happens on the frontend before sending to the backend API, which receives standard FirewallRule creation requests.

Service Presets

Referenced in line 12, service presets are defined in servicePresets.ts and include common services:

  • Web: https, http, dns
  • Remote Access: ssh, rdp
  • Databases: mysql, postgresql, mongodb, redis
  • Email: smtp, pop3, imap
  • File Sharing: ftp, sftp, nfs, smb

Each preset defines:

  • Protocol (tcp, udp, etc.)
  • Port number or range
  • Direction (IN, OUT, INOUT)
  • Description

Templates reference these presets by ID, allowing consistent service definitions across multiple templates and reducing duplication.

Inheritance Mechanism

How Inheritance Works

The firewall system implements inheritance through libvirt's nwfilter <filterref> mechanism:

  1. VM Filter XML: Contains <filterref filter="ibay-department-xyz"/> element
  2. Automatic Evaluation: Libvirt automatically evaluates the referenced department filter's rules
  3. Rule Combination: Department rules and VM rules are combined during packet filtering
  4. Automatic Updates: Changes to the department filter automatically affect all VMs without updating VM filters
  5. No Manual Synchronization: The parent-child relationship is maintained by libvirt, not application code

This design provides centralized control (change department rules once, affect all VMs) while allowing VM-specific customization.

Filter Priority

Rule evaluation is determined by the per-rule priority attribute in the generated XML. The backend sets ruleset base priorities to 1000 for departments and 500 for VMs, but the actual evaluation order depends on the individual rule priority values:

  • Lower priority number = higher priority (evaluated first)
  • Individual rule priority: 0-1000 (configurable per rule)
  • Rule evaluation: Rules from both department and VM filters are sorted by their priority attributes

To override a department rule from a VM:

  • Set the VM rule's priority to a lower number (higher priority) than the department rule's priority
  • Set the overridesDept=true flag to document the override intent
  • Example: If a department rule has priority 500, set the VM override rule to priority 400

XML Structure Example

Here's how inheritance appears in nwfilter XML:

<!-- Department Filter (ibay-department-a1b2c3d4) -->
<filter name="ibay-department-a1b2c3d4" chain="root">
  <uuid>12345678-1234-1234-1234-123456789abc</uuid>
  <rule action="accept" direction="in" priority="500">
    <tcp dstportstart="443" dstportend="443"/>
  </rule>
  <rule action="drop" direction="in" priority="1000">
    <tcp dstportstart="22" dstportend="22"/>
  </rule>
</filter>

<!-- VM Filter (ibay-vm-e5f6g7h8) -->
<filter name="ibay-vm-e5f6g7h8" chain="root">
  <uuid>87654321-4321-4321-4321-cba987654321</uuid>

  <!-- This filterref creates the inheritance relationship -->
  <filterref filter="ibay-department-a1b2c3d4"/>

  <!-- VM-specific rules -->
  <rule action="accept" direction="in" priority="500">
    <tcp dstportstart="8080" dstportend="8080"/>
  </rule>
</filter>

In this example:

  • The VM inherits department rules (HTTPS allowed, SSH blocked)
  • The VM adds its own rule (allow port 8080)
  • Effective rules: HTTPS (443) allowed, SSH (22) blocked, port 8080 allowed

Override Mechanism

VM rules can explicitly override department rules using the overridesDept flag:

Scenario: Department blocks port 80 (HTTP), but a specific VM needs to allow it.

Department Rule:

{
  action: 'DROP',
  direction: 'IN',
  protocol: 'tcp',
  dstPortStart: 80,
  dstPortEnd: 80,
  priority: 500
}

VM Override Rule:

{
  action: 'ACCEPT',
  direction: 'IN',
  protocol: 'tcp',
  dstPortStart: 80,
  dstPortEnd: 80,
  priority: 400,  // Higher priority than department rule
  overridesDept: true  // Explicit override flag
}

The VM rule:

  • Uses higher priority (400 vs 500, lower number = higher priority)
  • Sets overridesDept=true to document the override intent
  • Matches the same traffic pattern as the department rule
  • Uses opposite action (ACCEPT vs DROP)

Validation ensures the override rule actually targets an existing department rule, preventing nonsensical configurations.

Filter Naming Convention

Format

From the FilterNameGenerator utility, filter names follow a consistent pattern:

ibay-{type}-{hash}

Where:

  • ibay: Prefix identifying Infinibay-managed filters
  • type: Entity type ('department' or 'vm')
  • hash: MD5 hash of the entity ID, truncated to 8 characters

Examples

Department Filter:

  • Entity ID: dept-123e4567-e89b-12d3-a456-426614174000
  • MD5 Hash: c3fcd3d76192e4007dfb496cca67e13b → truncated to c3fcd3d7
  • Filter Name: ibay-department-c3fcd3d7

VM Filter:

  • Entity ID: vm-987fcdeb-51a2-43f8-9876-fedcba987654
  • MD5 Hash: 5f4dcc3b5aa765d61d8327deb882cf99 → truncated to 5f4dcc3b
  • Filter Name: ibay-vm-5f4dcc3b

Benefits

This naming convention provides several advantages:

  • Consistency: Same entity ID always generates the same filter name across system restarts and calls
  • Identification: The ibay- prefix makes Infinibay filters easily identifiable in libvirt
  • Type Safety: The type component ('department' or 'vm') indicates the filter's purpose
  • Deterministic: No randomness - filter name is derived from entity ID using cryptographic hash
  • Short Length: 8-character hash keeps names concise while maintaining low collision probability
  • Cleanup-Friendly: Easy to list all Infinibay filters using prefix matching (virsh nwfilter-list | grep ibay-)

Implementation

The FilterNameGenerator utility:

  • Takes entity type and entity ID as input
  • Computes MD5 hash of entity ID
  • Truncates hash to 8 hex characters
  • Concatenates prefix, type, and hash
  • Returns deterministic filter name

This centralized naming logic ensures all components (FirewallManager, strategies, XML generator) use identical naming.

Database Schema

FirewallRuleSet Model

From schema.prisma lines 633-652, the FirewallRuleSet model serves as a container for firewall rules:

Fields:

  • id: String, primary key, CUID
  • name: Human-readable name
  • internalName: Unique filter name (e.g., ibay-vm-a1b2c3d4) used as unique constraint
  • entityType: Enum (DEPARTMENT or VM) indicating which entity type owns this ruleset
  • entityId: String, the entity's database ID (combined with entityType for uniqueness)
  • priority: Int, base priority for the filter
  • isActive: Boolean, whether the ruleset is active (indexed for queries)
  • libvirtUuid: Unique string, the UUID returned by libvirt after defining the filter
  • xmlContent: Text field storing the last generated XML (for auditing and debugging)
  • lastSyncedAt: DateTime, timestamp of last synchronization with libvirt

Relationships:

  • department: Optional 1:1 relationship with Department model (inverse: department.firewallRuleSet)
  • machine: Optional 1:1 relationship with Machine model (inverse: machine.firewallRuleSet)
  • rules: 1:many relationship with FirewallRule model (cascade delete)

Unique Constraints:

  • internalName: Ensures no duplicate filter names
  • [entityType, entityId]: Ensures each entity has at most one ruleset
  • libvirtUuid: Ensures UUID uniqueness in database matching libvirt's UUID uniqueness

Indexes:

  • isActive: Supports fast queries for active rulesets

Purpose: The FirewallRuleSet acts as a container and metadata store, linking firewall rules to their owning entity (department or VM) and tracking synchronization state with libvirt.

FirewallRule Model

From schema.prisma lines 654-678, the FirewallRule model represents individual firewall rules:

Fields:

  • id: String, primary key, CUID
  • ruleSetId: Foreign key to FirewallRuleSet, cascade delete
  • name: Human-readable rule name
  • description: Detailed explanation of rule purpose
  • action: Enum (ACCEPT, DROP, REJECT)
  • direction: Enum (IN, OUT, INOUT)
  • priority: Int, rule evaluation priority (0-1000, lower = higher priority)
  • protocol: String, protocol name (tcp, udp, icmp, all, etc.)
  • srcPortStart: Int, source port range start (nullable)
  • srcPortEnd: Int, source port range end (nullable)
  • dstPortStart: Int, destination port range start (nullable)
  • dstPortEnd: Int, destination port range end (nullable)
  • srcIpAddr: String, source IP address (nullable)
  • srcIpMask: Int, source IP CIDR mask (nullable)
  • dstIpAddr: String, destination IP address (nullable)
  • dstIpMask: Int, destination IP CIDR mask (nullable)
  • connectionState: Json, array of connection states (NEW, ESTABLISHED, RELATED, INVALID) (nullable)
  • overridesDept: Boolean, whether this VM rule overrides a department rule (default: false)

Relationships:

  • ruleSet: Many:1 relationship with FirewallRuleSet (cascade delete ensures rules are removed when ruleset is deleted)

Indexes:

  • [ruleSetId, priority]: Supports fast queries for rules sorted by priority within a ruleset

Purpose: Each FirewallRule represents a single filtering criterion with all necessary parameters for XML generation. Rules are owned by a FirewallRuleSet and deleted automatically when the ruleset is deleted.

Enums

From schema.prisma lines 680-695:

RuleSetType (lines 680-683):

enum RuleSetType {
  DEPARTMENT
  VM
}

RuleAction (lines 685-689):

enum RuleAction {
  ACCEPT
  DROP
  REJECT
}

RuleDirection (lines 691-695):

enum RuleDirection {
  IN
  OUT
  INOUT
}

These enums provide type safety at the database level and ensure only valid values are stored.

Lifecycle and Synchronization

Creation Flow

The firewall infrastructure is created in multiple phases:

Phase 1: Entity Creation

  1. User creates a Department via GraphQL API
  2. Prisma afterCreate callback triggers
  3. Callback calls FirewallManager.ensureFirewallInfrastructure('DEPARTMENT', departmentId, description)
  4. FirewallManager creates FirewallRuleSet in database
  5. FirewallManager creates empty nwfilter in libvirt via FirewallFilterFactory
  6. Result: Department has empty firewall infrastructure ready for rules

Phase 2: VM Creation

  1. User creates a VM via GraphQL API
  2. Prisma afterCreate callback triggers
  3. Callback calls FirewallManager.ensureFirewallInfrastructure('VM', vmId, description)
  4. FirewallManager creates FirewallRuleSet in database
  5. FirewallManager creates empty nwfilter in libvirt
  6. Result: VM has empty firewall infrastructure

Phase 3: VM Definition

  1. CreateMachineService runs during VM provisioning
  2. Service calls FirewallManager.ensureFirewallForVM(vmId, departmentId) (verification phase)
  3. FirewallManager verifies department filter exists, creates if missing
  4. FirewallManager verifies VM filter exists, creates if missing
  5. Service calls FirewallManager.getFilterNames(vmId) to get filter names
  6. Service calls xmlGenerator.addVMNWFilter(vmFilterName) to add filter to VM domain XML
  7. Libvirt applies the filter when VM starts
  8. Result: VM has complete firewall protection with inheritance

Phase 4: XML Integration

  1. XMLGenerator includes the VM filter name in the network interface definition
  2. VM domain XML contains: <filterref filter='ibay-vm-e5f6g7h8'/>
  3. Only the VM filter is referenced in the domain XML
  4. The VM filter's XML contains: <filterref filter='ibay-department-a1b2c3d4'/>
  5. Libvirt automatically loads both filters when the VM starts
  6. Note: Libvirt does NOT support multiple <filterref> elements in VM domain XML - inheritance must be in the filter definition

Rule Modification Flow

When users modify firewall rules:

Phase 1: Database Update

  1. User adds/updates/deletes a rule via GraphQL API (e.g., createFirewallRule mutation)
  2. GraphQL resolver calls FirewallRuleService.createRule/updateRule/deleteRule
  3. FirewallRuleService performs database operation using Prisma
  4. Database now contains updated rules, but libvirt is not yet updated

Phase 2: Flush to Libvirt

  1. Frontend calls flushFirewallRules mutation (or backend triggers automatically)
  2. Mutation handler calls FirewallFilterFactory.ensureDepartmentFilter or ensureVMFilter
  3. Factory queries entity with all current rules from database
  4. Factory delegates to appropriate strategy (DepartmentFilterStrategy or VMFilterStrategy)
  5. Strategy calls FirewallValidationService.validateRuleConflicts to validate rules
  6. Strategy calls NWFilterXMLGeneratorService.generateFilterXML with current rules
  7. Strategy calls LibvirtNWFilterService.defineFilter with generated XML
  8. Libvirt redefines the filter using the same UUID (in-place update)
  9. Libvirt automatically applies updated filter to running VMs

Result: Running VMs immediately receive updated firewall rules without restart.

Note: Database synchronization fields (lastSyncedAt, xmlContent) are available for future tracking but are not automatically updated in the current implementation.

Synchronization

The system provides mechanisms for tracking synchronization state:

Timestamps:

  • FirewallRuleSet.lastSyncedAt: Database field for recording when rules were last synchronized to libvirt
  • FirewallRuleService.updateRuleSetSyncStatus method available for updating this field
  • Can be used for auditing and detecting drift when implemented

XML Storage:

  • FirewallRuleSet.xmlContent: Database field for storing the last generated XML sent to libvirt
  • Enables comparison between database state and libvirt state when populated
  • Useful for debugging and forensic analysis

UUID Tracking:

  • FirewallRuleSet.libvirtUuid: Links database record to libvirt filter
  • Ensures consistency between database and libvirt
  • Used for filter redefinition (updating existing filters without changing UUID)

Manual Resynchronization:

  • FirewallManager.resyncVMFirewall(vmId): Manual resync operation
  • Regenerates filters from database rules and redefines in libvirt
  • Useful for recovering from manual libvirt changes or detected drift

Integration with VM Creation

CreateMachineService Integration

From createMachineService.ts, the firewall system integrates with VM creation:

Phase 6: Firewall Verification (lines 114-124)

During VM provisioning, CreateMachineService performs firewall verification:

  1. Ensure Firewall Infrastructure: Calls FirewallManager.ensureFirewallForVM(vmId, departmentId)

    • Validates VM exists and belongs to specified department
    • Ensures department filter exists in libvirt (creates if missing)
    • Ensures VM filter exists in libvirt with proper inheritance (creates if missing)
    • Returns filter names and creation status
  2. Get Filter Names: Calls FirewallManager.getFilterNames(vmId)

    • Retrieves both department and VM filter names
    • No resource creation, only name generation
  3. Add Filter to XML: Calls xmlGenerator.addVMNWFilter(vmFilterName)

    • Adds only the VM filter to network interface
    • VM filter already contains <filterref> to department filter
    • Libvirt handles inheritance automatically

This verification phase ensures the firewall is properly configured before the VM is defined in libvirt, preventing VMs from being created without network security.

XMLGenerator Integration

The XMLGenerator service integrates the firewall filter into VM domain XML:

addVMNWFilter(vmFilterName):

  • Adds <filterref filter='ibay-vm-e5f6g7h8'/> to network interface elements
  • Filter applied to all network interfaces (both network and bridge types)
  • Priority 200 for VM filter (lower priority than department filter)

Important Constraint:

  • Libvirt does NOT support multiple <filterref> elements in VM domain XML
  • Only the VM filter name is added to the domain XML
  • The VM filter's own XML contains <filterref filter='ibay-department-xyz'/>
  • Inheritance is handled in the filter definition, not in the VM domain XML

Why This Design:

  • Simplifies VM domain XML (single filter reference)
  • Centralizes inheritance logic in filter definitions
  • Allows department filter updates without touching VM domain XML
  • Follows libvirt's recommended pattern for filter inheritance

Error Handling and Self-Healing

Self-Healing Scenarios

From FirewallManager, the system implements several self-healing mechanisms:

Broken Foreign Keys (lines 211-236)

Scenario: FirewallRuleSet exists in database but isn't linked to its entity via foreign key.

Detection: Query entity, find it has no firewallRuleSetId despite ruleset existing.

Recovery:

  1. Detect orphaned ruleset by querying FirewallRuleSet with matching entityType and entityId
  2. Update entity record with firewallRuleSetId foreign key
  3. Log the repair operation
  4. Continue normal operation

Cause: Race conditions during entity creation, manual database modifications, or Prisma callback failures.

Missing Department Filter (lines 454-476)

Scenario: VM filter creation fails because department filter doesn't exist in libvirt.

Detection: VMFilterStrategy calls LibvirtNWFilterService.filterExists(departmentFilterName) and gets false.

Recovery:

  1. Catch the error from VM filter creation
  2. Call FirewallFilterFactory.ensureDepartmentFilter(departmentId) to create missing department filter
  3. Retry VM filter creation
  4. Log the recovery operation

Cause: Department filter was manually deleted from libvirt, system crash during department creation, or libvirt connection issues.

Race Conditions (FirewallRuleService upsert)

Scenario: Multiple concurrent requests try to create the same FirewallRuleSet.

Detection: Prisma callback and manual call both try to create ruleset simultaneously.

Recovery:

  1. Use Prisma's upsert operation with internalName as unique key
  2. First request creates the record
  3. Subsequent requests receive the existing record
  4. No errors thrown, no duplicate records created

Cause: Concurrent API requests, Prisma callback race with manual creation, or retry logic.

Error Types

The system defines specific error types for different failure scenarios:

VM Not Found:

  • Trigger: Query for VM returns null
  • Error Message: "VM with ID {vmId} not found"
  • Recovery: Cannot recover, throw error to caller

Department Not Assigned:

  • Trigger: VM exists but departmentId is null
  • Error Message: "VM {vmId} has no department assigned"
  • Recovery: Cannot recover, requires department assignment

Department ID Mismatch:

  • Trigger: Provided departmentId doesn't match VM's actual department
  • Error Message: "Provided department ID {providedId} does not match VM's actual department {actualId}"
  • Recovery: Cannot recover, caller must use correct department ID

Department Filter Missing:

  • Trigger: VM filter creation fails because department filter doesn't exist in libvirt
  • Error Message: "Department filter {filterName} does not exist in libvirt for department {departmentId}. Create department filter first."
  • Recovery: Auto-recovery available (see Missing Department Filter above)

Filter Already Exists:

  • Trigger: Attempt to create filter that already exists in libvirt
  • Handling: Gracefully handled, redefines filter with same UUID
  • Result: No error thrown, existing filter updated

Graceful Degradation

The system handles partial failures gracefully:

Empty Filters Are Valid:

  • Creating a department or VM with no rules is valid and common
  • Empty filters generate minimal XML with just the filter name and UUID
  • Validation passes for empty rule arrays
  • Useful for progressive configuration (create infrastructure, add rules later)

Validation Pass for Empty Arrays:

  • FirewallValidationService.validateRuleConflicts([]) returns empty conflicts array
  • No rules means no conflicts
  • Enables infrastructure creation before rules are defined

Partial Success Tracking:

  • Filter creation returns result objects with success/failure status
  • Individual rule validation failures don't prevent filter creation
  • Errors are collected and returned to caller for review
  • System continues operating with valid rules

Continue on Non-Critical Errors:

  • Filter redefinition continues even if existing UUID lookup fails (generates new UUID)
  • Missing parent filter triggers recovery, doesn't halt entire operation
  • Validation warnings (e.g., ports on portless protocols) don't block creation

Best Practices

Rule Design

Use Department Rules for Organization-Wide Policies:

  • Define baseline security policies at department level
  • Examples: Block all database ports for marketing department, allow only HTTPS outbound
  • Changes automatically propagate to all VMs in department
  • Reduces duplication and ensures consistency

Use VM Rules for VM-Specific Exceptions:

  • Add VM-specific rules for unique requirements
  • Examples: Allow port 8080 for development VM, permit database access for reporting VM
  • Minimize VM-specific rules to keep configuration simple
  • Document exceptions clearly in rule descriptions

Set Explicit Priorities to Control Evaluation Order:

  • Assign priority values deliberately, not randomly
  • Lower priority number = higher priority (evaluated first)
  • Group related rules with similar priorities (e.g., 100-199 for infrastructure, 200-299 for applications)
  • Leave gaps between priorities for future insertions (use 100, 200, 300 instead of 1, 2, 3)

Use overridesDept Flag for Contradictory Rules:

  • Set overridesDept=true when VM rule explicitly contradicts department rule
  • Ensures higher priority (lower number) to override department rule
  • Validation checks that override actually targets a department rule
  • Documents intent clearly in database

Validate Rules Before Applying:

  • Always call FirewallValidationService.validateRuleConflicts before creating rules
  • Check for port overlaps, contradictory rules, and duplicates
  • Review validation warnings and errors
  • Fix conflicts before applying to production

Performance

Minimize Number of Rules:

  • Combine similar rules where possible
  • Example: Instead of 10 rules for ports 80, 81, 82, ..., 89, use one rule with port range 80-89
  • Fewer rules = faster evaluation in libvirt
  • Reduces XML size and memory usage

Use Port Ranges Instead of Multiple Single-Port Rules:

  • Port ranges are evaluated as efficiently as single ports
  • Example: Ports 8000-9000 instead of 1000 separate rules
  • Cleaner configuration, easier to understand
  • Reduces rule count significantly

Avoid Overlapping Rules:

  • Overlapping rules cause confusion about which rule matches
  • May impact performance due to duplicate evaluation
  • Validation service detects overlaps, fix them
  • Use more specific rules or consolidate overlapping rules

Use Templates for Common Configurations:

  • Templates provide pre-validated rule sets
  • Reduces manual errors and inconsistencies
  • Faster to apply than creating rules individually
  • Templates are tested and optimized

Security

Default Deny Policy:

  • Block all traffic by default, explicitly allow needed services
  • Create high-priority DROP/REJECT rule for all traffic
  • Add lower-priority ACCEPT rules for specific services
  • Prevents accidental exposure of services

Principle of Least Privilege:

  • Only open required ports for necessary services
  • Close ports when services are no longer needed
  • Regularly audit rules and remove unnecessary ones
  • Avoid "allow all" rules except for specific use cases

Use Connection State Tracking:

  • Leverage connection states (ESTABLISHED, RELATED) for stateful filtering
  • Example: Allow outbound connections, accept only ESTABLISHED/RELATED inbound
  • Prevents unsolicited inbound connections
  • More secure than allowing all inbound traffic

Regular Audits:

  • Review firewall rules periodically (monthly or quarterly)
  • Check for unused rules and remove them
  • Verify rules still align with current requirements
  • Document rule purposes in description field for easier audits

Test Rules Before Production:

  • Use test VMs to verify rules work as expected
  • Test both allowed and blocked traffic
  • Verify rule priorities evaluate in correct order
  • Check for unintended side effects

Maintenance

Document Rule Purpose in Description Field:

  • Write clear, concise descriptions explaining why the rule exists
  • Include ticket numbers or references if applicable
  • Examples: "Allow HTTPS for web application (TICKET-123)", "Block SSH from internet for security (SEC-456)"
  • Future maintainers will thank you

Use Meaningful Rule Names:

  • Name rules descriptively, not generically
  • Examples: "Allow HTTPS Inbound", "Block Database Outbound" instead of "Rule 1", "Rule 2"
  • Makes rule lists easier to scan and understand
  • Assists in debugging and auditing

Group Related Rules with Similar Priorities:

  • Use priority ranges for logical groupings
  • Example: 100-199 for infrastructure (DNS, DHCP), 200-299 for applications, 300-399 for databases
  • Makes rule ordering intuitive
  • Simplifies insertion of new rules

Use Templates for Consistency Across VMs:

  • Apply the same template to VMs with similar purposes
  • Ensures consistent security posture
  • Reduces configuration drift
  • Simplifies bulk updates (update template, reapply)

Monitor Blocked Connections:

  • Review BlockedConnection records regularly
  • Identify legitimate traffic being blocked
  • Adjust rules to allow necessary traffic
  • Detect potential security incidents (unusual blocked connections)

Troubleshooting

Common Issues

Filter Not Applied to VM

Symptoms: VM doesn't respect firewall rules, traffic not filtered

Diagnosis:

  1. Check FirewallRuleSet.lastSyncedAt - is it recent?
  2. Check if filter exists in libvirt: virsh nwfilter-list | grep ibay-vm-{hash}
  3. Check VM domain XML contains filter reference: virsh dumpxml {vm-name} | grep filterref

Solutions:

  • Call FirewallManager.resyncVMFirewall(vmId) to regenerate and redefine filters
  • Verify filter exists in libvirt, recreate if missing
  • Check VM domain XML, regenerate if filter reference is missing
  • Restart VM if filter was added after VM started

Rules Not Working as Expected

Symptoms: Traffic allowed/blocked contrary to rules

Diagnosis:

  1. Verify filter exists in libvirt: virsh nwfilter-list
  2. Check filter XML: virsh nwfilter-dumpxml ibay-vm-{hash}
  3. Verify rule priority order (lower number = higher priority)
  4. Check for contradictory rules (ACCEPT and DROP for same traffic)
  5. Test connectivity from another VM or host

Solutions:

  • Verify rules in database match rules in libvirt XML
  • Check rule priorities, ensure higher-priority rule matches first
  • Look for overlapping rules, consolidate or adjust priorities
  • Use FirewallValidationService to check for conflicts
  • Flush rules to libvirt: call flushFirewallRules mutation

Department Filter Missing

Symptoms: VM filter creation fails with "department filter does not exist" error

Diagnosis:

  1. Check if department filter exists: virsh nwfilter-list | grep ibay-department-{hash}
  2. Check FirewallRuleSet exists for department in database
  3. Check department has firewallRuleSetId foreign key

Solutions:

  • Call FirewallManager.ensureFirewallInfrastructure('DEPARTMENT', departmentId, description)
  • Call FirewallFilterFactory.ensureDepartmentFilter(departmentId)
  • If self-healing doesn't work, manually create department infrastructure

VM Filter Creation Fails

Symptoms: VM creation succeeds but firewall setup fails

Diagnosis:

  1. Check error message for specific failure reason
  2. Verify VM has department assigned (vm.departmentId not null)
  3. Verify department filter exists (see above)
  4. Check libvirt connection is active

Solutions:

  • Ensure VM belongs to a department before creating filter
  • Ensure department filter exists (auto-recovery should handle this)
  • Check libvirt connection: restart libvirt if necessary
  • Retry VM filter creation after fixing underlying issue

Port Still Blocked Despite ACCEPT Rule

Symptoms: Port blocked even though ACCEPT rule exists

Diagnosis:

  1. Check rule priority - is there a higher-priority DROP/REJECT rule?
  2. Check for contradictory rules in department vs VM
  3. Verify rule direction (IN vs OUT)
  4. Check rule protocol matches traffic protocol
  5. Verify port range includes the blocked port

Solutions:

  • Adjust rule priority to be higher (lower number) than blocking rule
  • Remove or adjust contradictory rules
  • Ensure direction is correct (IN for inbound, OUT for outbound)
  • Verify protocol matches (TCP vs UDP)
  • Use FirewallValidationService to detect conflicts

Orphaned Filters in Libvirt

Symptoms: Filters exist in libvirt but not in database, or vice versa

Diagnosis:

  1. List all Infinibay filters: virsh nwfilter-list | grep ibay-
  2. Compare with database FirewallRuleSet records
  3. Identify filters in libvirt without database records
  4. Identify database records without libvirt filters

Solutions:

  • Clean up orphaned libvirt filters: LibvirtNWFilterService.cleanupAllInfinibayFilters()
  • Resync database records to libvirt: call resyncVMFirewall for each VM
  • Recreate missing filters from database: call ensureFirewallForVM or ensureDepartmentFilter
  • Investigate root cause (manual deletions, crash during creation, etc.)

Debugging Commands

List All Filters:

virsh nwfilter-list

Shows all nwfilters in libvirt, including Infinibay filters (ibay-*).

Show Filter XML:

virsh nwfilter-dumpxml ibay-vm-e5f6g7h8

Displays the complete XML definition of a filter, including rules and parent filter references.

Check VM Network Interface:

virsh dumpxml {vm-name} | grep filterref

Shows which filter is applied to the VM's network interface.

Test Connectivity:

# From another VM or host
nc -zv {vm-ip} {port}        # TCP connectivity test
ping {vm-ip}                 # ICMP connectivity test
telnet {vm-ip} {port}        # Interactive TCP test

Tests if specific ports are accessible through the firewall.

Check Filter Exists:

virsh nwfilter-list | grep ibay-vm-{hash}

Verifies that a specific filter exists in libvirt.

List Infinibay Filters Only:

virsh nwfilter-list | grep ibay-

Shows only Infinibay-managed filters.

Validation

Validate Rules Before Applying:

const conflicts = await firewallValidationService.validateRuleConflicts(rules);
if (conflicts.length > 0) {
  console.error('Rule conflicts detected:', conflicts);
  // Fix conflicts before proceeding
}

Check for Port Overlaps:

const overlaps = conflicts.filter(c => c.type === 'PORT_OVERLAP');
overlaps.forEach(overlap => {
  console.log(`Overlap: ${overlap.message}`);
  // Review and consolidate overlapping rules
});

Verify IP Addresses, Ports, and Protocol Constraints:

const inputErrors = await firewallValidationService.validateRuleInput(rule);
if (inputErrors.length > 0) {
  console.error('Invalid rule configuration:', inputErrors);
  // Fix IP addresses, CIDR masks, port ranges, or protocol constraints
}

Validate Override Rules:

// Validate a single VM rule against department rules
for (const vmRule of vmRules) {
  if (vmRule.overridesDept) {
    const overrideErrors = await firewallValidationService.validateOverride(
      vmRule,
      departmentRules
    );
    if (overrideErrors.length > 0) {
      console.error('Invalid override rule:', overrideErrors);
      // Ensure override rule targets an actual department rule
    }
  }
}

Architecture Diagram

graph TD
    A[GraphQL API] --> B[FirewallManager<br/>Facade Layer]
    B --> C[FirewallFilterFactory<br/>Factory Layer]
    C --> D[DepartmentFilterStrategy]
    C --> E[VMFilterStrategy]

    D --> F[NWFilterXMLGeneratorService]
    D --> G[LibvirtNWFilterService]
    D --> H[FirewallValidationService]

    E --> F
    E --> G
    E --> H

    B --> I[FirewallRuleService<br/>Data Access Layer]
    I --> J[(Prisma)]
    J --> K[(PostgreSQL)]

    G --> L[Libvirt Daemon]
    L --> M[nwfilter Subsystem]

    N[User] --> A

    style N fill:#64748b,stroke:#475569,stroke-width:2px,color:#fff
    style A fill:#64748b,stroke:#475569,stroke-width:2px,color:#fff
    style B fill:#0ea5e9,stroke:#0284c7,stroke-width:2px,color:#fff
    style C fill:#f59e0b,stroke:#d97706,stroke-width:2px,color:#000
    style D fill:#a855f7,stroke:#9333ea,stroke-width:2px,color:#fff
    style E fill:#a855f7,stroke:#9333ea,stroke-width:2px,color:#fff
    style F fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff
    style G fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff
    style H fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff
    style I fill:#ef4444,stroke:#dc2626,stroke-width:2px,color:#fff
    style J fill:#8b5cf6,stroke:#7c3aed,stroke-width:2px,color:#fff
    style K fill:#8b5cf6,stroke:#7c3aed,stroke-width:2px,color:#fff
    style L fill:#64748b,stroke:#475569,stroke-width:2px,color:#fff
    style M fill:#ec4899,stroke:#db2777,stroke-width:2px,color:#fff

Rule Evaluation Flow

Note: The system does not enforce a default-deny policy automatically. If no rule matches a packet, libvirt takes no action. For a default-deny security posture, explicitly add a low-priority catch-all DROP rule in department templates (e.g., priority 1000, action DROP, protocol all).

sequenceDiagram
    participant VM as Virtual Machine
    participant Libvirt as Libvirt Daemon
    participant VMFilter as VM Filter<br/>(ibay-vm-xyz)
    participant DeptFilter as Department Filter<br/>(ibay-department-abc)
    participant Packet as Network Packet

    VM->>Libvirt: VM starts / packet arrives
    Libvirt->>VMFilter: Load VM filter from domain XML
    VMFilter->>DeptFilter: Load parent filter via <filterref>

    Note over Libvirt: Evaluate Rules by Priority
    Libvirt->>DeptFilter: Evaluate department rules
    DeptFilter->>DeptFilter: Sort rules by priority
    DeptFilter->>Packet: Check each rule against packet

    alt Packet matches department rule
        DeptFilter->>Libvirt: Apply action (ACCEPT/DROP/REJECT)
    else No department rule matches
        Libvirt->>VMFilter: Evaluate VM rules
        VMFilter->>VMFilter: Sort rules by priority
        VMFilter->>Packet: Check each rule against packet

        alt Packet matches VM rule
            VMFilter->>Libvirt: Apply action (ACCEPT/DROP/REJECT)
        else No rule matches
            Note over Libvirt: No action unless catch-all rule exists
            Libvirt->>Packet: No explicit policy enforcement
        end
    end

    Libvirt->>VM: Packet allowed/blocked based on action

XML Generation Process

flowchart TD
    A[Start: User creates/updates rules] --> B[FirewallRuleService saves to database]
    B --> C[User calls flushFirewallRules mutation]
    C --> D[FirewallManager.ensureFirewallForVM]
    D --> E[Query VM with department and rules]
    E --> F[FirewallFilterFactory.ensureVMFilter]
    F --> G[VMFilterStrategy.createFilter]

    G --> H{VM has department?}
    H -->|No| I[Throw Error: No department assigned]
    H -->|Yes| J[Check department filter exists in libvirt]

    J --> K{Department filter exists?}
    K -->|No| L[Throw Error: Create department filter first]
    K -->|Yes| M[FirewallValidationService.validateRuleConflicts]

    M --> N{Conflicts detected?}
    N -->|Yes| O[Return conflicts to user]
    N -->|No| P[NWFilterXMLGeneratorService.generateFilterXML]

    P --> Q[Sort rules by priority]
    Q --> R[Add filterref to department filter]
    R --> S[Generate rule elements]

    S --> T{For each rule}
    T --> U[Map action to lowercase]
    U --> V[Map direction IN/OUT/INOUT]
    V --> W[Add connection state if specified]
    W --> X[Add protocol attributes ports, IPs]
    X --> Y[Create protocol element tcp, udp, etc.]
    Y --> T

    T -->|All rules processed| Z[Build XML with xml2js.Builder]
    Z --> AA[LibvirtNWFilterService.defineFilter]
    AA --> AB[Libvirt creates/updates nwfilter]
    AB --> AC[Return filter UUID]
    AC --> AD[End: Filter applied to libvirt]

    style I fill:#ef4444,stroke:#dc2626,stroke-width:2px,color:#fff
    style L fill:#ef4444,stroke:#dc2626,stroke-width:2px,color:#fff
    style O fill:#f59e0b,stroke:#d97706,stroke-width:2px,color:#000
    style AD fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff