Skip to main content

Guide: deny-others rules

The deny-others rule is one of Invariant's most powerful tools for enforcing network segmentation and Zero Trust principles. It allows you to define a broad scope of traffic that should be denied by default, with specific, explicitly permitted exceptions. This guide will walk you through understanding, writing, and troubleshooting deny-others rules.

The core purpose of a deny-others rule is to implement a "default deny" posture for a defined segment of your network. You specify what traffic should be allowed, and Invariant verifies that all other traffic within a defined scope is indeed blocked.

Key Components:

  • Target Network (ingress-network or egress-network): Defines the primary network segment the rule applies to. For ingress-deny-others, this is the destination network. For egress-deny-others, this is the source network.
  • Scope of Denial (within clause - Optional): Narrows down the type of traffic that the "deny by default" logic applies to. If omitted, it applies to all traffic targeting (for ingress) or originating from (for egress) the network.
  • Explicit Permissions (deny-all-except.flows clause - Required): A list of specific traffic flows that are permitted within the defined scope.

High-Level Evaluation Logic:

Invariant evaluates a deny-others rule by performing an exhaustive search:

  1. Identify Target Network: Resolves the ingress-network or egress-network to specific IP spaces.
  2. Identify Scope of Denial: Resolves the traffic characteristics defined in the within clause (e.g., specific protocols, ports, source/destination IPs). If within is not present, the scope is all traffic.
  3. Identify Explicitly Permitted Flows: Resolves the flows listed in deny-all-except.flows.
  4. Search for Violations: Invariant searches for any traffic flow that:
    • Matches the Target Network.
    • Falls within the Scope of Denial (matches the within clause).
    • Is NOT part of the Explicitly Permitted Flows (does not match any flow in deny-all-except.flows).
    • AND can be successfully delivered through the network (i.e., passes all routing and firewall configurations).
  5. Violation: If such a flow is found, the deny-others rule is violated because traffic that should have been denied (as it wasn't explicitly permitted) was allowed.

Use Cases:

  • Securing sensitive zones (e.g., PCI CDE, databases) by allowing only minimal necessary access.
  • Implementing Zero Trust network segmentation by defining what traffic is allowed and denying everything else.
  • Gradually migrating to a Zero Trust model by using enforce: false to discover existing allowed traffic (see Build a Zero Trust deployment backlog).

Writing deny-others Rules

Let's construct an ingress-deny-others rule to control SSH access to a DATACENTER network.

Defining the Target Network

First, specify the network segment this rule protects. For an ingress rule, this is the destination.

access-policy:
- name: datacenter-ssh-control
ingress-network: DATACENTER # DATACENTER is a named network, e.g., 10.1.0.0/16
# ...

Defining the Scope of Denial (within clause)

The within clause defines the broader category of traffic you intend to control with this rule. All traffic matching within will be denied unless explicitly allowed by deny-all-except.

  • Purpose: To narrow down what traffic is subject to the "deny by default" part of the rule.
  • Common Scenarios:
    • Scoping by protocol: within: [{protocol: tcp}] (controls all TCP traffic to DATACENTER).
    • Scoping by protocol and port: within: [{protocol: tcp, destination-port: SSH}] (controls all SSH traffic to DATACENTER).
    • Scoping by source and protocol: within: [{source-address: RFC1918, protocol: tcp}] (controls all internal TCP traffic to DATACENTER).
  • Note: If within is omitted, the scope is all traffic to/from the target network. This is very broad and usually desired only if deny-all-except is also very comprehensive.

For our SSH example, we want to control all TCP traffic destined for the SSH port on our DATACENTER network:

# ...
rules:
- type: ingress-deny-others
comment: Only MGMT_SERVER can SSH into DATACENTER
within:
- protocol: tcp
destination-port: SSH # Assumes SSH is defined as tcp/22
# ...

Defining Explicit Permissions (deny-all-except.flows clause)

This clause lists the specific flows that are allowed, creating exceptions to the denial defined by ingress-network and within.

  • Structure: A list of flow definitions. Each flow can specify source-address, destination-address (though usually redundant for ingress rules if ingress-network is specific), destination-port, source-port, and protocol.

We want to allow SSH only from MGMT_SERVER:

# ...
deny-all-except:
flows:
- comment: Allow SSH from MGMT_SERVER to DATACENTER
source-address: MGMT_SERVER # e.g., 192.168.1.10/32
protocol: tcp
destination-port: SSH

Complete Example Rule

access-policy:
- name: datacenter-ssh-control
comment: Controls SSH access into the DATACENTER network
owner: security-team@example.com
ingress-network: DATACENTER
rules:
- type: ingress-deny-others
comment: Only MGMT_SERVER can SSH into DATACENTER
within:
- protocol: tcp
destination-port: SSH
deny-all-except:
flows:
- comment: Allow SSH from MGMT_SERVER to DATACENTER
source-address: MGMT_SERVER
protocol: tcp
destination-port: SSH

This rule states: "For any TCP traffic going to the SSH port on any host in DATACENTER, deny it, unless it is TCP traffic from MGMT_SERVER to the SSH port."

Best Practices

  • Start Broad, Permit Specific: It's often easier to define a broader within scope (e.g., all TCP traffic to a sensitive zone) and then list the few necessary permitted flows in deny-all-except.
  • Use enforce: false: When creating new deny-others rules or migrating to a Zero Trust model, start with enforce: false. This allows Invariant to report "violations" (i.e., traffic that is currently allowed but would be denied by this rule if enforced) without failing your CI/CD pipeline. Use the policy_violations_unenforced report to build a backlog of flows to explicitly allow or block.
  • Be Precise: Ensure your network and service definitions (def/ directory) are accurate. A typo in an IP address or service name in deny-all-except can lead to unintended denials.

Understanding Violations and policy_details

When a deny-others rule is violated, it means Invariant found a traffic flow that:

  1. Matched the ingress-network (or egress-network).
  2. Matched the within clause (if specified).
  3. Did not match any flow in deny-all-except.flows.
  4. Was successfully deliverable through the network.

Violations are reported in policy_violations (for enforced rules) or policy_violations_unenforced. The policy_details report provides the crucial context.

Key fields in policy_details for a deny-others violation:

  • ok: false: Indicates a rule violation.
  • flow_str: A human-readable summary of the specific example flow that violated the rule. This is the traffic that was permitted but should have been denied according to the rule's logic.
    • Example: tcp src_ip=10.5.5.5 dst_ip=10.1.0.50 dst_port=22
  • start: The entry point of the violating flow into the network model (e.g., @enter(edge-router[GigabitEthernet0/1])).
  • traces: A list of one or more detailed paths (virtual traceroutes) the violating flow took through the network.

Interpreting Traceroutes (traces section)

The traces section is vital for understanding why a violating flow was permitted. Each trace represents a possible path:

  • Structure: A list of hops. Each hop object contains:
    • node: The hostname of the device.
    • steps: A list of actions/evaluations the packet underwent on that node.
  • Key steps to analyze:
    • RECEIVED(interface): Packet arrival on an interface.
    • PERMITTED(filter='filter_name', filterType='INGRESS_FILTER'|'EGRESS_FILTER'): Packet was allowed by the specified ACL or firewall filter.
    • FORWARDED(..., routes=[...], resolvedNextHopIp='...'): Routing decision made. Shows the route(s) used and the resolved next-hop IP.
    • ACCEPTED(interface): Packet delivered to the destination IP on the final node, via the specified local interface.

By examining the traces, you can pinpoint which ACL allowed the unintended traffic or if routing misconfigurations led the traffic to bypass an intended security control.

Example Scenario: If your deny-others rule for DATACENTER (denying all TCP except from MGMT_SERVER) is violated by a flow from USER_SUBNET to DATACENTER_SERVER_A on TCP/8080, the traceroute might show:

  1. Packet from USER_SUBNET arrives at dist-switch-1.
  2. dist-switch-1 has an ACL VLAN_INBOUND_ACL that PERMITTED the flow (perhaps it has an permit ip any any rule).
  3. dist-switch-1 FORWARDED the packet towards DATACENTER_SERVER_A.
  4. Packet ACCEPTED by DATACENTER_SERVER_A.

This trace tells you that VLAN_INBOUND_ACL on dist-switch-1 needs to be tightened.

Understanding policy_logs

The policy_logs report provides a summary for every rule Invariant processed, including deny-others rules.

Key fields for deny-others rules in policy_logs:

  • ok: The overall pass (true) or fail (false) status of the rule.
  • skipped: true if the rule could not be evaluated due to syntax errors, unresolvable network/service names, or other issues. errors will contain details.
  • errors: A list of error messages if the rule was skipped or encountered problems during evaluation.
  • violations: The number of unique example flows found that violate the rule. For a passing deny-others rule, this will be 0.
  • checks: The number of distinct search queries or checks Invariant performed to validate this rule. For a deny-others rule, this typically involves Invariant constructing a query to find any flow that (matches target network AND matches within AND does NOT match deny-all-except.flows) and IS deliverable.
  • check_log: (Accessible via JSON output) Provides a detailed list of the underlying checks. For deny-others, this would show the parameters of the reachability search Invariant performed.

How policy_logs helps:

  • Confirmation: Verifies that your rule was understood and processed by Invariant.
  • Scope of Test: The checks count gives an idea of the comprehensiveness of Invariant's validation for that rule.
  • Debugging Skipped Rules: If a rule is skipped, the errors field is the first place to look for why.

Common Pitfalls and Troubleshooting

  • Overly Broad within: If within is too general (e.g., just protocol: tcp) and deny-all-except is not exhaustive, you might inadvertently block legitimate traffic that wasn't considered.
  • Overly Narrow within: If within is too specific, the "deny by default" part of the rule might not cover traffic you intended to block. For example, within: [{protocol: tcp, destination-port: HTTP}] will only control HTTP; other TCP traffic to the target network won't be affected by this specific deny-others rule.
  • Typos in deny-all-except: A misspelled service name or incorrect IP in deny-all-except.flows means that flow won't be correctly exempted, potentially leading to it being flagged as a violation (if it's deliverable) or actually blocked if the rule is enforced.
  • Router Interface IPs: If your ingress-network defines a subnet (e.g., 10.1.1.0/24), and a router interface (e.g., SVI for that VLAN) has an IP within that subnet (e.g., 10.1.1.1), traffic to that router interface IP might be evaluated. If your ACLs are on the router interface itself, you might want to use destination-exclude in ingress-network to exclude the router's own IPs from the target definition if the intent is to protect hosts behind the router.
  • Interaction of source-address in within:
    • Policy: ingress-network: DATACENTER_A
    • Rule: within: [{source-address: CORP_SUBNET_B}]
    • This means the rule's scope of denial applies to traffic from CORP_SUBNET_B destined to DATACENTER_A. Only flows from CORP_SUBNET_B to DATACENTER_A will be denied unless listed in deny-all-except.

By understanding these components and how Invariant reports on them, you can effectively use deny-others rules to build and maintain a robust, verifiable network security posture.