Skip to main content

Guide: critical-flow rules

Critical-flow rules in Invariant are designed to assert that specific, essential network traffic must always be successfully delivered. Unlike simply checking for a single path, Invariant exhaustively analyzes your network's digital twin to find any scenario—be it routing configurations, ACLs, or firewall policies—that could prevent this critical traffic from reaching its destination. This provides a strong guarantee for the reachability of your vital services.

How Invariant Evaluates Critical Flow Rules

The evaluation process involves several key steps:

  1. Digital Twin Creation: Invariant, leveraging Batfish, constructs a detailed model of your network from the provided configuration files.
  2. Definition Resolution: Any named networks (e.g., DATABASE_SERVERS), services (e.g., HTTPS), or locations (e.g., DMZ_ENTRY_POINTS) used in your rule are resolved to their concrete IP addresses, ports, and interfaces.
  3. Start Location Inference:
    • For egress-critical-flow rules, if source-interface or enter-interface is not explicitly defined, Invariant intelligently infers potential starting interfaces based on the source-address.
    • For ingress-critical-flow rules, the ingress-network definition implicitly defines the destination, and Invariant searches for flows from any valid source towards it.
  4. Exhaustive Flow Search: Invariant performs a comprehensive search for all possible packets that match your critical flow definition. It looks for any instance where such a packet could be dropped, denied, or misrouted.
  5. Outcome:
    • If any packet defined by the rule fails to be delivered via any possible path, the critical-flow rule is marked as a violation.
    • If all packets defined by the rule can be successfully delivered across all potential paths, the rule passes.

Defining Critical Flow Rules

Critical flow rules are defined within an access-policy block in your invariant/policies/ YAML files.

Key characteristics and fields:

  • type: Must be either ingress-critical-flow (for traffic destined to the policy's ingress-network) or egress-critical-flow (for traffic originating from the policy's egress-network).
  • Traffic Specification: Uses fields like protocol, source-address, destination-address, source-port, destination-port to define the traffic.
  • Interface Specificity (Optional):
    • enter-interface: For egress-critical-flow, specifies that traffic must originate by entering these specific interfaces (useful for testing traffic from external sources or specific network segments).
    • source-interface: For egress-critical-flow, specifies that traffic must originate from these specific interfaces (using their configured IPs).

Example ingress-critical-flow:

This rule asserts that any host should be able to reach the DNS_SERVERS network on UDP port 53.

access-policy:
- name: core-dns-reachability
comment: Ensure DNS servers are reachable
owner: netops@example.com
ingress-network: DNS_SERVERS # Target network for ingress
rules:
- type: ingress-critical-flow
comment: All internal networks must reach DNS
source-address: RFC1918 # From any internal IP
destination-port: DNS # Standard DNS service
protocol: udp

Example egress-critical-flow:

This rule asserts that hosts in APP_SERVERS_VLAN must be able to connect to DATABASE_SERVERS on TCP port 1433.

access-policy:
- name: app-to-db-connectivity
comment: Critical application to database flow
owner: app-team@example.com
egress-network: APP_SERVERS_VLAN # Source network for egress
rules:
- type: egress-critical-flow
comment: App servers must reach database servers on SQL port
destination-address: DATABASE_SERVERS
destination-port: MSSQL_TCP # Assuming MSSQL_TCP is defined as TCP/1433
protocol: tcp

Interpreting Results

After an invariant run, you'll primarily look at these reports:

  • critical_flows_ok: Lists all critical flow rules that passed.
  • critical_flows_violations: Lists all enforced critical flow rules that failed.
  • critical_flows_violations_unenforced: Lists failing critical flow rules that were marked with enforce: false.
  • critical_flows_details: This is the most crucial report for debugging. It provides example virtual traceroutes for both passing and failing rules, showing why a flow succeeded or failed.

Strategic Application

  • Core Services: Ensure core network services like DNS, NTP, and authentication servers are always reachable.
  • Application Connectivity: Validate critical application-to-application or application-to-database communication paths.
  • Broad vs. Narrow Rules:
    • Broad rules (e.g., "any internal user to any web server on HTTPS") provide wide coverage but can be harder to debug if they fail due to the many potential paths and policies involved.
    • Narrow/Canary rules (e.g., "specific_critical_server_A to specific_critical_server_B on specific_port") are easier to pinpoint failures for and are excellent for high-priority services.
  • Complement Deny Rules: Critical flow rules ensure what must work, while deny or deny-others rules ensure what must not work, providing a comprehensive security and connectivity posture.

Understanding Traceroutes

Invariant's virtual traceroutes provide a deep, hop-by-hop analysis of how packets traverse your network model. This is far more detailed than a live traceroute.

Accessing Traceroutes: The critical_flows_details Report

The critical_flows_details report is your primary tool for understanding the behavior of critical flow rules. Each row in this report corresponds to a specific check or an example flow Invariant analyzed for one of your rules.

Key fields in critical_flows_details:

  • flow / flow_str: Describes the specific example packet (source/destination IPs, ports, protocol, etc.) that was traced.
  • traces: An array containing one or more trace paths. Multiple paths can exist if Equal-Cost Multi-Path (ECMP) routing is in play.
  • disposition (within each trace): The final outcome for that specific path (e.g., ACCEPTED, DENIED_IN, NO_ROUTE).

Decoding a Trace

Each trace within the traces array is a list of hops. Each hop represents a device (router, firewall, switch) in the packet's path.

Each hop contains a list of steps, detailing the packet's processing on that device:

  • RECEIVED(Interface): Indicates the interface on which the packet arrived at the current hop.
    • Example: {"action": "RECEIVED", "detail": {"inputInterface": "GigabitEthernet0/0"}}
  • PERMITTED(FilterName (FilterType)): The packet was allowed by the specified ACL or firewall rule (FilterName) applied at a particular stage (FilterType, e.g., INGRESS_FILTER).
    • Example: {"action": "PERMITTED", "detail": {"filter": "acl_allow_ssh (INGRESS_FILTER)"}}
  • DENIED(FilterName (FilterType)): The packet was blocked by the specified filter. This is a common reason for critical flow rule violations. The trace ends here for this path.
    • Example: {"action": "DENIED", "detail": {"filter": "acl_block_all (EGRESS_FILTER)"}}
  • FORWARDED(Forwarded out interface: ..., Routes: [...]): A routing decision was made.
    • outputInterface: The interface the packet will be sent out of.
    • resolvedNexthopIp: The next-hop IP address.
    • routes: A list of routes from the RIB that matched the packet's destination.
    • Example: {"action": "FORWARDED", "detail": {"outputInterface": "Serial0/0", "resolvedNexthopIp": "10.1.1.2", "routes": [{"protocol": "ospf", "network": "0.0.0.0/0", ...}]}}
  • NO_ROUTE(Discarded): No route was found in the RIB for the packet's destination. The packet is dropped. This is another common reason for critical flow violations.
    • Example: {"action": "NO_ROUTE", "detail": {"type": "Discarded"}}
  • NEIGHBOR_UNREACHABLE(Discarded): The next-hop IP address was unresolvable (e.g., ARP or ND failure in the model). Packet dropped.
  • ACCEPTED(Interface): The packet was delivered to an interface on the device itself (e.g., destined for a loopback IP, or a service running on the router). This is a successful disposition.
  • DELIVERED_TO_SUBNET(Output Interface: ..., Resolved Next Hop IP: ...): The packet was successfully forwarded to the directly connected subnet of the destination IP. This is a successful disposition.
  • EXITS_NETWORK(Output Interface: ..., Resolved Next Hop IP: ...): The packet was forwarded out of an interface that leads to a network segment not modeled by Invariant (e.g., towards the internet via an ISP link). This is generally a successful disposition if the destination is external.

Analyzing Traces for Critical Flows

  • Passing Rule: For a rule in critical_flows_ok, the traces in critical_flows_details will show paths ending in a successful disposition like ACCEPTED, DELIVERED_TO_SUBNET, or EXITS_NETWORK (if appropriate).
  • Failing Rule: For a rule in critical_flows_violations, the traces will show the exact hop and step where the failure occurred (e.g., a DENIED step by an ACL, or a NO_ROUTE step).

Example: Interpreting a critical_flows_details Snippet

Let's consider a simple critical flow rule:

# Invariant/policies/example.yaml
access-policy:
- name: web-server-access
owner: web-team@example.com
ingress-network: WEB_SERVER_SUBNET
rules:
- type: ingress-critical-flow
comment: Ensure internal users can access web server via HTTPS
source-address: INTERNAL_USERS_SUBNET
destination-port: HTTPS
protocol: tcp

Scenario 1: Passing Rule

A snippet from critical_flows_details (in JSON) for a passing flow might look like:

{
"flow_str": "tcp src_ip=192.168.1.50 dst_ip=10.10.10.100 dst_port=443",
"traces": [
{
"disposition": "DELIVERED_TO_SUBNET",
"hops": [
// ... initial hops ...
{
"node": "core-switch-01",
"steps": [
{"action": "RECEIVED", "detail": {"inputInterface": "GigabitEthernet1/0/1"}},
{"action": "PERMITTED", "detail": {"filter": "ALLOW_INTERNAL_ACCESS (INGRESS_FILTER)"}},
{"action": "FORWARDED", "detail": {"outputInterface": "GigabitEthernet1/0/24", "resolvedNexthopIp": "10.10.10.100", "routes": [{"protocol": "connected", "network": "10.10.10.0/24", ...}]}},
{"action": "TRANSMITTED", "detail": {"outputInterface": "GigabitEthernet1/0/24"}}
]
},
{
"node": "web-server-01.example.com", // Assuming host modeling
"steps": [
{"action": "RECEIVED", "detail": {"inputInterface": "eth0"}},
{"action": "ACCEPTED", "detail": {"interface": "eth0"}} // Delivered to host
]
}
]
}
// ... potentially other successful paths if ECMP exists ...
]
}

This trace shows traffic from 192.168.1.50 (part of INTERNAL_USERS_SUBNET) successfully reaching 10.10.10.100 (part of WEB_SERVER_SUBNET) on port 443. It was permitted by the ALLOW_INTERNAL_ACCESS filter on core-switch-01 and finally ACCEPTED by the web server.

Scenario 2: Failing Rule (ACL Denial)

A snippet for a failing flow, for instance, if an ACL unexpectedly blocks the traffic:

{
"flow_str": "tcp src_ip=192.168.1.50 dst_ip=10.10.10.100 dst_port=443",
"traces": [
{
"disposition": "DENIED_IN",
"hops": [
// ... initial hops ...
{
"node": "firewall-01",
"steps": [
{"action": "RECEIVED", "detail": {"inputInterface": "inside_interface"}},
{"action": "DENIED", "detail": {"filter": "BLOCK_UNTRUSTED_HTTPS (INGRESS_FILTER)"}} // Failure point
]
}
]
}
]
}

In this case, the traffic was DENIED by the filter BLOCK_UNTRUSTED_HTTPS on firewall-01. This immediately tells you where to investigate the misconfiguration.

Edge Case: No Traceroute Generated

Occasionally, a critical flow rule might fail, but critical_flows_details might not show a full traceroute, or the trace might be very short (e.g., only showing the origin node with a NO_ROUTE disposition). This typically means:

  • No Route at Origin: The starting device/location for the flow doesn't even have a route towards the destination. The packet can't begin its journey.
  • Misconfigured Start Location: The source-address, enter-interface, or source-interface in your rule might not resolve to any valid points in your network model from which the flow could realistically start.

Carefully examine the flow details and the initial hops (if any) to diagnose such issues.