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(fromservices/firewall/FirewallManager.ts) provides a simplified interface that coordinates complex multi-step operations across multiple services, hiding implementation details from callers. - Strategy Pattern:
DepartmentFilterStrategyvsVMFilterStrategy(fromservices/firewall/strategies/) implement different filter creation logic while sharing a common interface, allowing the system to treat both entity types polymorphically. - Factory Pattern:
FirewallFilterFactory(fromservices/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:
FirewallManagersimplifies complex operations like VM firewall initialization, providing clean APIs for common workflows. - Factory Layer:
FirewallFilterFactorycreates appropriate strategy instances and provides facade methods for common operations. - Strategy Layer:
DepartmentFilterStrategyandVMFilterStrategyimplement entity-specific filter creation logic, handling differences in XML generation and inheritance. - Service Layer:
FirewallRuleService: Database CRUD operations for rules and rule setsNWFilterXMLGeneratorService: Converts database rules to libvirt nwfilter XMLLibvirtNWFilterService: Interfaces with libvirt nwfilter subsystemFirewallValidationService: 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
FirewallRuleSetin the database usingFirewallRuleService.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.lastSyncedAttimestamps - 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
FirewallRuleSetexists 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
FirewallRuleServiceupsert logic to handle concurrent rule set creation from Prisma callbacks.
Integration with Prisma Callbacks
The FirewallManager is called by Prisma lifecycle callbacks:
afterCreateDepartment: CallsensureFirewallInfrastructureto create department firewall infrastructureafterCreateMachine: CallsensureFirewallInfrastructureto 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→ CreatesDepartmentFilterStrategyinstanceRuleSetType.VM→ CreatesVMFilterStrategyinstance- 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
createFiltermethod - 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
FirewallRulerecords - Validates that the department has a
FirewallRuleSet - Calls
DepartmentFilterStrategy.createFilterwith 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
FirewallRulerecords - Validates that the VM has a department assigned
- Validates that the VM has a
FirewallRuleSet - Calls
VMFilterStrategy.createFilterwith 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 rulesLibvirtNWFilterService: Libvirt nwfilter operationsFirewallValidationService: Rule conflict and validation checksPrismaClient: 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)
- Validate Rules: Call
FirewallValidationService.validateRuleConflictsto check for conflicts. Empty arrays pass validation and are common for new departments. - Generate Filter Name: Use
FilterNameGeneratorto create deterministic name (e.g.,ibay-department-a1b2c3d4). - Get Existing UUID: Query libvirt for existing filter UUID to support redefinition (updating existing filters).
- Generate XML: Call
NWFilterXMLGeneratorService.generateFilterXMLwithout parent filter reference. - Define in Libvirt: Call
LibvirtNWFilterService.defineFilterto create/update the filter. - 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)
- Query VM: Retrieve VM with department relation from database.
- Ensure Department Filter Exists: Verify department filter exists in libvirt (lines 72-81). Throws descriptive error if missing.
- Validate VM Rules: Call
FirewallValidationService.validateRuleConflictsto check for conflicts. - Generate VM Filter Name: Use
FilterNameGeneratorto create name (e.g.,ibay-vm-e5f6g7h8). - Get Existing UUID: Query libvirt for existing filter UUID to support redefinition.
- Generate XML with Parent: Call
NWFilterXMLGeneratorService.generateFilterXMLwithparentFilterNameparameter to create inheritance. - Define in Libvirt: Call
LibvirtNWFilterService.defineFilterto create/update the filter. - 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
internalNameas 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
FirewallRuleSetwith rules included - Filters by entity type and ID
- Returns array of
FirewallRuleobjects 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
FirewallRulerecords - Returns
FirewallRuleSetwith 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
libvirtUuidfield with the filter UUID from libvirt - Updates
xmlContentfield with the generated XML - Updates
lastSyncedAttimestamp 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:
- Call
createRuleSetto create the ruleset - Update the entity record with
firewallRuleSetIdforeign key - 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:
- Sort Rules by Priority: Lower priority numbers are evaluated first (higher priority).
- Create Filter Object: Builds the root filter structure with:
$: Attributes object withnameandchain='root'uuid: Uses existing UUID for redefinition or generates new one
- Add Parent Filter Reference: If
parentFilterNameis provided (for VM filters), adds<filterref filter="parent-name"/>element (lines 49-58). - Convert Rules to Elements: Calls
convertRuleToXMLElementfor each rule to generate rule elements. - Build XML: Uses
xml2js.Builderto 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:
- Map Action: Converts database enum to lowercase (ACCEPT → accept, DROP → drop, REJECT → reject).
- Map Direction: Converts database enum (IN → in, OUT → out, INOUT → inout).
- Add Connection State: If
rule.connectionStateJSON contains states (NEW, ESTABLISHED, RELATED, INVALID), addsstatematchattribute. - Create Attributes Object: Builds rule attributes including action, direction, priority, connection state.
- 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.)
- 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
listAllInfinibayFiltersto get all Infinibay filters - Iterates through filters and calls
undefineFilterfor 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
trueif the lookup succeeds - Returns
falseif 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:
filterExistsreturnsfalseinstead of throwing errors (lines 115-119)getFilterUuidreturnsnullinstead 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=trueactually 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.isIPandnet.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):
- Range Comparison: Compares source and destination port ranges between rules
- Overlap Calculation: Determines if ranges overlap (start1 ≤ end2 && start2 ≤ end1)
- Overlap Details: Calculates which specific ports overlap
- Action-Based Suggestions: Provides different suggestions based on whether rules have same or different actions
- 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 namedescription: Detailed explanation of the rule's purposeaction: ACCEPT, DROP, or REJECTdirection: IN, OUT, or INOUTpriority: 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
FirewallRuleobjects 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 namedescription: Explanation of template purposeicon: Icon identifier for UIcategory: Template category (server, desktop, development, database)servicePresets: Array of service preset IDs (references toservicePresets.ts)customRules: Additional rules not covered by service presetspriority: 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:
- Expands Service Presets: Looks up each preset ID in
servicePresetsconfiguration and extracts its rules - Combines with Custom Rules: Merges service preset rules with template's custom rules
- Applies Template Priority: Sets the template's base priority on all rules
- Returns Rule Array: Returns array of
TemplateRuleobjects 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:
- VM Filter XML: Contains
<filterref filter="ibay-department-xyz"/>element - Automatic Evaluation: Libvirt automatically evaluates the referenced department filter's rules
- Rule Combination: Department rules and VM rules are combined during packet filtering
- Automatic Updates: Changes to the department filter automatically affect all VMs without updating VM filters
- 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
priorityattributes
To override a department rule from a VM:
- Set the VM rule's
priorityto a lower number (higher priority) than the department rule's priority - Set the
overridesDept=trueflag 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=trueto 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 filterstype: 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 toc3fcd3d7 - Filter Name:
ibay-department-c3fcd3d7
VM Filter:
- Entity ID:
vm-987fcdeb-51a2-43f8-9876-fedcba987654 - MD5 Hash:
5f4dcc3b5aa765d61d8327deb882cf99→ truncated to5f4dcc3b - 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, CUIDname: Human-readable nameinternalName: Unique filter name (e.g.,ibay-vm-a1b2c3d4) used as unique constraintentityType: Enum (DEPARTMENT or VM) indicating which entity type owns this rulesetentityId: String, the entity's database ID (combined with entityType for uniqueness)priority: Int, base priority for the filterisActive: Boolean, whether the ruleset is active (indexed for queries)libvirtUuid: Unique string, the UUID returned by libvirt after defining the filterxmlContent: 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 withDepartmentmodel (inverse:department.firewallRuleSet)machine: Optional 1:1 relationship withMachinemodel (inverse:machine.firewallRuleSet)rules: 1:many relationship withFirewallRulemodel (cascade delete)
Unique Constraints:
internalName: Ensures no duplicate filter names[entityType, entityId]: Ensures each entity has at most one rulesetlibvirtUuid: 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, CUIDruleSetId: Foreign key toFirewallRuleSet, cascade deletename: Human-readable rule namedescription: Detailed explanation of rule purposeaction: 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 withFirewallRuleSet(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
- User creates a Department via GraphQL API
- Prisma
afterCreatecallback triggers - Callback calls
FirewallManager.ensureFirewallInfrastructure('DEPARTMENT', departmentId, description) FirewallManagercreatesFirewallRuleSetin databaseFirewallManagercreates empty nwfilter in libvirt viaFirewallFilterFactory- Result: Department has empty firewall infrastructure ready for rules
Phase 2: VM Creation
- User creates a VM via GraphQL API
- Prisma
afterCreatecallback triggers - Callback calls
FirewallManager.ensureFirewallInfrastructure('VM', vmId, description) FirewallManagercreatesFirewallRuleSetin databaseFirewallManagercreates empty nwfilter in libvirt- Result: VM has empty firewall infrastructure
Phase 3: VM Definition
CreateMachineServiceruns during VM provisioning- Service calls
FirewallManager.ensureFirewallForVM(vmId, departmentId)(verification phase) FirewallManagerverifies department filter exists, creates if missingFirewallManagerverifies VM filter exists, creates if missing- Service calls
FirewallManager.getFilterNames(vmId)to get filter names - Service calls
xmlGenerator.addVMNWFilter(vmFilterName)to add filter to VM domain XML - Libvirt applies the filter when VM starts
- Result: VM has complete firewall protection with inheritance
Phase 4: XML Integration
XMLGeneratorincludes the VM filter name in the network interface definition- VM domain XML contains:
<filterref filter='ibay-vm-e5f6g7h8'/> - Only the VM filter is referenced in the domain XML
- The VM filter's XML contains:
<filterref filter='ibay-department-a1b2c3d4'/> - Libvirt automatically loads both filters when the VM starts
- 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
- User adds/updates/deletes a rule via GraphQL API (e.g.,
createFirewallRulemutation) - GraphQL resolver calls
FirewallRuleService.createRule/updateRule/deleteRule FirewallRuleServiceperforms database operation using Prisma- Database now contains updated rules, but libvirt is not yet updated
Phase 2: Flush to Libvirt
- Frontend calls
flushFirewallRulesmutation (or backend triggers automatically) - Mutation handler calls
FirewallFilterFactory.ensureDepartmentFilterorensureVMFilter - Factory queries entity with all current rules from database
- Factory delegates to appropriate strategy (
DepartmentFilterStrategyorVMFilterStrategy) - Strategy calls
FirewallValidationService.validateRuleConflictsto validate rules - Strategy calls
NWFilterXMLGeneratorService.generateFilterXMLwith current rules - Strategy calls
LibvirtNWFilterService.defineFilterwith generated XML - Libvirt redefines the filter using the same UUID (in-place update)
- 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 libvirtFirewallRuleService.updateRuleSetSyncStatusmethod 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:
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
Get Filter Names: Calls
FirewallManager.getFilterNames(vmId)- Retrieves both department and VM filter names
- No resource creation, only name generation
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:
- Detect orphaned ruleset by querying
FirewallRuleSetwith matchingentityTypeandentityId - Update entity record with
firewallRuleSetIdforeign key - Log the repair operation
- 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:
- Catch the error from VM filter creation
- Call
FirewallFilterFactory.ensureDepartmentFilter(departmentId)to create missing department filter - Retry VM filter creation
- 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:
- Use Prisma's
upsertoperation withinternalNameas unique key - First request creates the record
- Subsequent requests receive the existing record
- 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
departmentIdis null - Error Message:
"VM {vmId} has no department assigned" - Recovery: Cannot recover, requires department assignment
Department ID Mismatch:
- Trigger: Provided
departmentIddoesn'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=truewhen 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.validateRuleConflictsbefore 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
BlockedConnectionrecords 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:
- Check
FirewallRuleSet.lastSyncedAt- is it recent? - Check if filter exists in libvirt:
virsh nwfilter-list | grep ibay-vm-{hash} - 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:
- Verify filter exists in libvirt:
virsh nwfilter-list - Check filter XML:
virsh nwfilter-dumpxml ibay-vm-{hash} - Verify rule priority order (lower number = higher priority)
- Check for contradictory rules (ACCEPT and DROP for same traffic)
- 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
FirewallValidationServiceto check for conflicts - Flush rules to libvirt: call
flushFirewallRulesmutation
Department Filter Missing
Symptoms: VM filter creation fails with "department filter does not exist" error
Diagnosis:
- Check if department filter exists:
virsh nwfilter-list | grep ibay-department-{hash} - Check
FirewallRuleSetexists for department in database - Check department has
firewallRuleSetIdforeign 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:
- Check error message for specific failure reason
- Verify VM has department assigned (
vm.departmentIdnot null) - Verify department filter exists (see above)
- 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:
- Check rule priority - is there a higher-priority DROP/REJECT rule?
- Check for contradictory rules in department vs VM
- Verify rule direction (IN vs OUT)
- Check rule protocol matches traffic protocol
- 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
FirewallValidationServiceto detect conflicts
Orphaned Filters in Libvirt
Symptoms: Filters exist in libvirt but not in database, or vice versa
Diagnosis:
- List all Infinibay filters:
virsh nwfilter-list | grep ibay- - Compare with database
FirewallRuleSetrecords - Identify filters in libvirt without database records
- Identify database records without libvirt filters
Solutions:
- Clean up orphaned libvirt filters:
LibvirtNWFilterService.cleanupAllInfinibayFilters() - Resync database records to libvirt: call
resyncVMFirewallfor each VM - Recreate missing filters from database: call
ensureFirewallForVMorensureDepartmentFilter - 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