Skip to main content

Guide: deny rules

Invariant deny rules are a powerful tool for enforcing your network security policy. They allow you to assert that specific types of traffic should never be successfully delivered between defined source and destination networks or locations.

Purpose of deny rules:

  • Explicitly block unwanted or high-risk traffic flows.
  • Enforce network segmentation and isolation between different security zones.
  • Verify that decommissioned services are no longer accessible.
  • Complement critical-flow rules (which assert reachability) and deny-others rules (which define an allow-list) by explicitly defining what must be forbidden.

How Invariant evaluates deny rules:

When Invariant evaluates a deny rule, it exhaustively searches your network model for any possible path that would allow the specified traffic to be successfully delivered. If even one such path exists, Invariant flags a violation. This comprehensive check ensures that there are no unintended backdoors or misconfigurations that could compromise your security posture.

When to use deny rules:

  • Broad Security Postures: To block all access from an untrusted zone (e.g., Guest Wi-Fi) to a sensitive internal zone (e.g., PCI Data).
  • Specific Micro-segmentation: To prevent a particular server from communicating with another specific server on a certain port.
  • Blocking Known Bad Traffic: To deny traffic to or from known malicious IP addresses or services.
  • Validating Decommissions: After removing a service, use a deny rule to confirm it's truly inaccessible.

Execution Model

Understanding how Invariant processes deny rules involves several key steps:

  1. Snapshot Processing: You begin by providing Invariant with a snapshot of your network. This includes:

    • Device configurations (e.g., routers, switches, firewalls).
    • Network and service definitions (typically in YAML files within the def/ directory).
    • Your Invariant policy files, including deny rules (in invariant/policies/). Invariant uses this information, leveraging the underlying Batfish analysis engine, to build a comprehensive digital twin of your network.
  2. Definition Resolution: Invariant resolves all named entities used in your rules:

    • Network Names: (e.g., INTERNAL_DB_SUBNET, UNTRUSTED_NETWORK) are translated into specific IP addresses or CIDR blocks based on your def/networks.yaml files.
    • Service Names: (e.g., SSH, HTTPS) are resolved to their corresponding protocols and port numbers from def/services.yaml.
    • Location Names: (e.g., EXTERNAL_BOUNDARY_INTERFACES) are mapped to specific device interfaces defined in invariant/locations/.

    The resolution follows a precedence: definitions local to a rule (if applicable) > snapshot-local definitions (def/) > definitions installed with Invariant > built-in definitions (like RFC1918, ANY).

  3. Start Location Inference (for Egress Deny Rules): For egress-deny rules, Invariant needs to determine where the traffic originates:

    • If source-interface or enter-interface is specified in the rule, Invariant uses those exact interfaces.
    • Otherwise, Invariant infers start locations based on the source-address. It identifies all interfaces whose configured IP addresses fall within the source-address range or whose primary network overlaps with it. The rule is then evaluated for traffic originating from these inferred interfaces.

    For ingress-deny rules, the ingress-network policy field (specifically its destination-address) defines the target network being protected. Invariant then considers traffic from the specified source-address (or ANY if unspecified) attempting to reach this ingress-network.

  4. Flow Search: This is the core of the evaluation. Invariant performs an exhaustive search across the digital twin. For a deny rule, it looks for any instance where a packet matching the rule's criteria (source/destination IP, port, protocol) successfully reaches its destination.

  5. Evidence Compilation & Output:

    • Violation Found: If Invariant finds any successful path for the traffic defined in the deny rule, it's a violation.
      • The policy_violations report will list the failing rule.
      • The policy_details report will provide crucial evidence:
        • The specific violating flow (e.g., exact source/destination IPs and ports).
        • A detailed virtual traceroute showing the successful path the packet took, including all hops, ACL decisions, and routing choices.
    • No Violation (Rule Passes): If the exhaustive search finds no way for the specified traffic to be delivered, the deny rule passes.
      • The rule will be listed in the policy_ok report.

Writing Deny Rules

Deny rules are defined in YAML files within your invariant/policies/ directory, as part of an access-policy.

Basic Structure:

invariant/policies/security_policy.yaml
access-policy:
- name: block-untrusted-to-internal-db
comment: "Prevent untrusted networks from accessing internal databases"
owner: security-team@example.com
# For an ingress-deny rule, ingress-network is the target being protected
ingress-network: INTERNAL_DB_SUBNET
rules:
- type: ingress-deny
comment: "Block any access from UNTRUSTED_NETWORK to internal DBs"
source-address: UNTRUSTED_NETWORK
# destination-address is implicitly INTERNAL_DB_SUBNET from the policy's ingress-network
# protocol and destination-port can be added for more specificity, e.g.:
# protocol: tcp
# destination-port: MYSQL_PORT

Ingress vs. Egress Deny Rules:

  • ingress-deny: Use this to protect a destination network (defined in the policy's ingress-network) from unwanted incoming traffic.

    • Example: Deny any external traffic (e.g., from EXTERNAL_LOCATION) from reaching internal management interfaces (MGMT_SUBNET).
      access-policy:
      - name: protect-management-interfaces
      ingress-network: MGMT_SUBNET
      rules:
      - type: ingress-deny
      comment: "Block external access to management interfaces"
      enter-interface: EXTERNAL_BOUNDARY_INTERFACES # Explicitly define entry point
      # source-address defaults to ANY if not specified from external location
      # destination-address is implicitly MGMT_SUBNET
  • egress-deny: Use this to prevent a source network (defined in the policy's egress-network) from sending traffic to undesirable destinations.

    • Example: Deny internal servers (INTERNAL_SERVERS_VLAN) from initiating any connections to known malicious IP ranges (KNOWN_MALICIOUS_IPS).
      access-policy:
      - name: block-outbound-to-malicious
      egress-network: INTERNAL_SERVERS_VLAN
      rules:
      - type: egress-deny
      comment: "Prevent servers from contacting known malicious IPs"
      # source-address is implicitly INTERNAL_SERVERS_VLAN
      destination-address: KNOWN_MALICIOUS_IPS

Specificity: Broad vs. Narrow Rules:

The granularity of your deny rules depends on your security objectives.

  • Broad Rules: Define general security boundaries and are excellent for establishing a baseline security posture.

    • Example: Deny all traffic from the GUEST_VLAN to the CORP_NETWORK.
      # In a policy with ingress-network: CORP_NETWORK
      - type: ingress-deny
      comment: "Isolate Guest VLAN from Corporate Network"
      source-address: GUEST_VLAN
      Or, equivalently, in a policy with egress-network: GUEST_VLAN:
      # In a policy with egress-network: GUEST_VLAN
      - type: egress-deny
      comment: "Isolate Guest VLAN from Corporate Network"
      destination-address: CORP_NETWORK
  • Narrow Rules: Target very specific protocols, ports, or subnets. Useful for highly targeted restrictions or blocking specific known vulnerabilities.

    • Example: Deny Telnet access from any RFC1918 address to a group of SENSITIVE_SERVERS.
      # In a policy with ingress-network: SENSITIVE_SERVERS
      - type: ingress-deny
      comment: "Block Telnet to sensitive servers from internal networks"
      source-address: RFC1918 # Built-in name for private IP space
      destination-port: TELNET # Built-in service name for TCP/23
      protocol: tcp

Choosing the right level of specificity is key. Start broad to establish major zone boundaries, then add narrower rules for specific risks or compliance requirements.

Understanding Violation Traceroutes

When a deny rule is violated, Invariant provides a virtual traceroute in the policy_details report. This traceroute is crucial because it shows exactly how the supposedly denied traffic managed to successfully reach its destination.

Accessing Traceroutes:

Use the Invariant CLI to view the details:

invariant show policy_details --json --snapshot <YOUR_SNAPSHOT_ID>

You'll then need to parse the JSON output, filtering for the specific rule that failed (e.g., by its policy.name and rule.comment).

Key JSON Fields in a Violation Traceroute:

  • flow: This object describes the specific packet that constituted the violation. It includes:

    • srcIp, dstIp: Source and Destination IP addresses.
    • srcPort, dstPort: Source and Destination ports (for TCP/UDP).
    • ipProtocol: The IP protocol (e.g., TCP, UDP, ICMP).
    • Example:
      "flow": {
      "dscp": 0,
      "dstIp": "10.1.1.5", // IP in INTERNAL_DB_SUBNET
      "dstPort": 3306, // MYSQL_PORT
      "ipProtocol": "TCP",
      "srcIp": "192.168.100.20", // IP in UNTRUSTED_NETWORK
      "srcPort": 54321,
      "tcpFlagsSyn": 1, // Indicates a TCP SYN packet (connection initiation)
      // ... other fields like icmpCode, icmpVar, packetLength etc.
      }
  • start: Indicates where this violating flow originated within the network model. This could be an interface on a device (e.g., @enter(edge-router-01[GigabitEthernet0/0])) or a modeled host.

  • traces: An array containing one or more paths the violating flow took. Often, there's just one trace, but multiple traces can appear if Equal-Cost Multi-Path (ECMP) routing is involved.

    • disposition: For a deny rule violation, the disposition of the trace will be ACCEPTED (or another success-indicating status like DELIVERED_TO_SUBNET or EXITS_NETWORK), signifying the traffic reached its destination.
    • hops: An array detailing each network device (hop) the packet traversed.
      • node: The hostname of the device.
      • steps: An array of processing steps the packet underwent on this node. Each step has an action and detail:
        • action: "RECEIVED": Packet arrived at an interface. detail.inputInterface shows which one.
        • action: "PERMITTED": Packet was allowed by a filter (ACL/firewall rule). detail.filter gives the filter name.
        • action: "FORWARDED": Packet was routed. detail.outputInterface shows the egress interface, detail.resolvedNexthopIp the next-hop IP, and detail.routes the routing table entry used.
        • action: "TRANSFORMED": Packet was modified (e.g., by NAT). detail.transformationType and detail.flowDiffs describe the change.
        • action: "ACCEPTED" (on the final hop): Packet delivered to an IP on the device itself or an attached host.

Interpreting the Path:

The traceroute allows you to pinpoint the misconfiguration:

  • Permissive ACLs: Look for PERMITTED steps through ACLs or firewall policies that should have blocked the traffic. The detail.filter field will name the culprit ACL.
  • Unexpected Routing: If the traffic takes an unusual path, bypassing intended security controls, examine the FORWARDED steps and the detail.routes to understand why that path was chosen.
  • NAT Issues: If NAT is involved (TRANSFORMED steps), ensure it's behaving as expected and not inadvertently allowing access.

Troubleshooting Deny Rule Violations

When a deny rule fails, follow these steps:

  1. Verify Definitions:

    • Are source-address, destination-address, destination-port, etc., in your rule resolving to the IP addresses, subnets, and services you intend?
    • Use def/networks.yaml and def/services.yaml to confirm.
    • The resolved_as field in the policy_details output for the failing rule shows how Invariant interpreted your named definitions.
  2. Analyze the Traceroute(s):

    • Carefully examine each hop and step in the traces provided in policy_details.
    • Identify any ACLs that PERMITTED the flow unexpectedly.
    • Note the routing decisions (FORWARDED steps) that led the packet along the violating path.
  3. Check Network Configurations:

    • Based on the traceroute, inspect the actual configurations of the involved devices (routers, firewalls).
    • Look for overly permissive ACL entries, incorrect routing policies, or NAT rules that might be causing the issue.
  4. Refine the Invariant Rule:

    • Too Broad? Is your deny rule so general that it's catching legitimate traffic that is correctly allowed by your network (and thus showing as a "violation" of your overly strict deny rule)? If so, make your deny rule more specific.
    • Too Narrow? Is your deny rule too specific, missing the actual pattern of traffic that's getting through? Broaden its scope or add more specific deny rules.
    • Adjust source-address, destination-address, protocol, destination-port, enter-interface etc., as needed to accurately reflect the traffic you intend to block.
  5. Adjust Network Device Configuration:

    • Modify the ACLs, routing policies, or firewall rules on the actual network devices to enforce the intended denial.
    • Upload a new snapshot with these changes to Invariant and re-run the analysis to confirm the violation is resolved and no new issues have been introduced.

Next Steps

  • Combine deny rules with critical-flow rules to ensure that while you block unwanted traffic, essential services remain accessible.
  • For a more comprehensive "default-deny" or "allow-list" security posture, explore using deny-others rules.

Potential Improvements (Self-Correction):

  • The "Start Location Inference" section could be slightly more explicit for ingress-deny rules, emphasizing that the ingress-network defines the destination being protected, and Invariant considers sources defined by source-address (or ANY) or enter-interface. Correction made in the generated MDX above.
  • A small, concrete example of a def/networks.yaml and def/services.yaml snippet alongside the basic rule structure could further clarify definition resolution. Added implicitly by using named definitions like INTERNAL_DB_SUBNET and SSH which imply they come from def/ files.
  • The traceroute JSON example could be slightly more complete to show a few hops, but the current snippets are focused and serve their purpose.