Note
In Suricata 8 the firewall mode is experimental and subject to change.
The firewall mode in Suricata allows the use of a ruleset that has different properties than the default "threat detection" rulesets:
- default policy is
drop, meaning a firewall ruleset needs to specify what is allowed - firewall rules are loaded from separate files
- firewall rules use a new action
accept - firewall rules are required to use explicit action scopes and rule hooks (see below)
- evaluation order is as rules are in the file(s), per protocol state
The interaction between firewall and TD is concepualized as if they are 2 seperate instances, where the firewall instance runs first, and it passes along to the TD instance what is accepted by the firewall.
This is reflected in the stats, where a packet accepted by the firewall is counted
as firewall.accepted. If it was also allowed by TD, it will additionally be
counted as ips.accepted. If it was dropped by firewall, only firewall.blocked
will be incremented. No ips.* counter will be updated as conceptually the TD
instance won't have seen the packet.
A table is a collection of rules with different properties. These tables are built-in.
No custom tables can be created. Tables are available within the scope of packet layer
and application layer (if available). Each rule can define its own :ref:`action scope<ips_action_scopes>`.
Rules categorized in the following tables apply to all packets.
| Table | Description | Default Policy | Rule Order |
|---|---|---|---|
packet:pre_flow |
Firewall rules to be evaluated before flow is created/updated | accept:hook |
As appears in the rule file |
packet:pre_stream |
Firewall rules to be evaluated before stream is updated | accept:hook |
As appears in the rule file |
packet:filter |
Firewall rules to be evaluated against every packet after decoding | drop:packet |
As appears in the rule file |
packet:td |
Generic IDS/IPS threat detection rules | accept:hook |
Internal IDS/IPS rule ordering |
If applayer is available, rules from the following tables apply. The tables for the
application layer are per app layer protocol and per protocol state. e.g. http1:request_line.
| Table | Description | Default Policy | Rule Order |
|---|---|---|---|
app:filter |
Firewall rules to be evaluated per applayer protocol and state | drop:flow |
As appears in the rule file |
app:td |
App-layer IDS/IPS threat detection rules | accept:hook |
Internal IDS/IPS rule ordering |
Firewall rules require action scopes to be explicitly specified.
accept is used to issue an accept verdict to the packet, flow or hook.
packetaccept this packetflowaccept the rest of the packets in this flowhookaccept rules for the current hook/state, evaluate the next tablestxaccept rules for the current transaction, evaluate the next tables
The accept action is only available in firewall rules.
Note
some protocol implementations like dns use a transaction per direction.
For those accept:tx will only accept packets that are part of that direction.
drop is used to drop either the packet or the flow
packetdrop this packet directly, don't eval any further rulesflowdrop this packet as withpacketand drop all future packets in this flow
Note
unlike in threat detection mode rules, a drop in a firewall rule does not
imply alert
pass is not available as a primary firewall action, but can be used as a secondary
action in firewall rules. The effect of the action will only apply to threat detection rules.
alert is not available as a primary firewall action, but can be used as a secondary
action in firewall rules. The effect will be the creation of an alert event when the
firewall rule matches.
In firewall rules, multiple actions can be specified: a primary firewall action, followed by one or more secondary actions.
Example:
accept:flow,pass:flow,alert tls:client_hello_done ... tls.sni; ...
In this example the first action accept:flow is the primary firewall action. When the
rule matches, the flow will be accepted. The secondary actions pass:flow and alert are
evaluated in the context of the threat detection engine.
Note
the secondary actions are only evaluated if the primary firewall action is accepted.
This is different from the behavior of the pass action in threat detection mode.
In the regular IDS/IPS rules the engine infers from the rule's matching logic where the rule should be "hooked" into the engine. While this works well for these types of rules, it does lead to many edge cases that are not acceptable in a firewall ruleset. For this reason in the firewall rules the hook needs to be explicitly set.
There are two types of hooks available based on the layer.
flow_start: evaluate the rule only on the first packet in both the directionspre_flow: evaluate the rule before the flow is created/updatedpre_stream: evaluate the rule before the stream is updatedall: evaluate the rule on every packet
The application layer states / hooks are defined per protocol. Each of the hooks has its own
default-drop policy, so a ruleset needs an accept rule for each of the states to allow
the traffic to flow through.
This is done in the protocol field of the rule. Where in threat detection a rule might look like:
alert http ... http.uri; ...
In the firewall case it will be:
accept:hook http1:request_line ... http.uri; ...
All available applayer hooks are available via commandline option --list-app-layer-hooks.
Each protocol has at least the default states.
Request (to_server) side:
request_startedrequest_complete
Response (to_client) side:
response_startedresponse_complete
For the HTTP protocol there are a number of states to hook into. These apply to HTTP 0.9, 1.0 and 1.1. HTTP/2 uses its own state machine.
Available states:
Request (to_server) side:
request_startedrequest_linerequest_headersrequest_bodyrequest_trailerrequest_complete
Response (to_client) side:
response_startedresponse_lineresponse_headersresponse_bodyresponse_trailerresponse_complete
Available states:
Request (to_server) side:
client_in_progressclient_hello_doneclient_cert_doneclient_handshake_doneclient_finished
Response (to_client) side:
server_in_progressserver_helloserver_cert_doneserver_hello_doneserver_handshake_doneserver_finished
Available states are listed in :ref:`ssh-hooks`.
To avoid creating lots of boilerplate accept rules there is a special notation to have
a rule accept not just the hook it matches in, but also the hooks before it.
Example:
accept:flow tls:<client_hello_done ... tls.sni; content:"suricata.io"; ...
The main matching logic here is in the tls:client_hello_done hook. The state before it,
tls:client_in_progress is also accepted, as if the ruleset was actually:
accept:hook tls:client_in_progress ... accept:flow tls:client_hello_done ... tls.sni; content:"suricata.io"; ...
This logic only applies to the app:filter table.
The firewall pipeline works in the detection engine, and is invoked after packet decoding, flow update, stream tracking and reassembly and app-layer parsing are all done in the context of a single packet.
For each packet rules in the first firewall hook packet:filter are then evaluated. Assuming
the verdict of this hook is accept:hook, the next hook is evaluated: packet:td (packet
threat detection). In this hook the IDS/IPS rules are evaluated. Rule actions here are not
immediate, as they can still be modified by alert postprocessing like rate_filter, thresholding, etc.
The default drop for the packet:filter table is drop:packet. Thus the drop is
only applied to the current packet.
If the packet has been marked internally as a packet with an application layer update, then the
next table is app:*:*.
In app:*:* the per application layer states are all evaluated at least once. At each of
these states an accept:hook is required to progress to the next state. When all available states
have been accepted, the pipeline moves to the final table app:td (application layer threat
detection). A drop in the app:filter table is immediate, however and accept is
conditional on the verdict of the app:td table.
The default drop in one of the app:*:* tables is a drop:flow. This means that the
current packet as well as all future packets from that flow are dropped.
In app:td the IDS/IPS rules for the application layer are evaluated. drop actions in this
table are queued in the alert queue.
When all tables have been evaluated, the alert finalize process orders threat detection alerts
by action-order logic. It can then apply a drop or default to accept-ing.
In IDS/IPS mode, a pass rule with app-layer matches will bypass the detection engine for the
rest of the flow. In firewall mode, this bypass no longer happens in the same way, as pass rules
do not affect firewall rules. So the detection engine is still invoked on packets of such a flow,
but the packet:td and app:td tables are skipped.
Firewall rules are loaded first and separately from the following section of suricata.yaml:
firewall-rule-path: /etc/suricata/firewall/ firewall-rule-files: - fw.rules
One can optionally, also load firewall rules exclusively from commandline using the
--firewall-rules-exclusive option. Note that this option blocks hot rule reloads, just like
the -S option in thread detection rules.
Firewall rules are available in the file firewall.json as a part of the output
of :ref:`engine analysis<config:engine-analysis>`.
The firewall mode can be used with capture methods in bridge and router mode. When using the bridge mode, the default drop policy will also apply to non-IP protocols, like ARP.
For ARP to work, a rule to accept it is required:
accept:packet arp:all any any -> any any (sid:200;)
Other ethernet types can be accepted by using generic ethernet rules, with the ether.hdr keyword.
The example below accepts ARP again, using this mechanism.
accept:packet ether:all any any -> any any (ether.hdr; content:"|08 06|"; offset:12; depth:2; sid:1;)
Each hook has a default policy applied to traffic that no firewall rule handled.
By default packet.filter enforces drop:packet, packet.pre-flow and
packet.pre-stream enforce accept:hook, and every app hook enforces
drop:flow.
Defaults are configured in the firewall.policies block. A default-policy
may be given at several levels; for any hook the most specific present setting
wins:
firewall:
policies:
default-policy: ["accept:hook"] # global fallback (all hooks)
packet:
default-policy: ["drop:packet"] # fallback for packet hooks
filter: ["drop:packet"]
pre-flow: ["accept:hook"]
pre-stream: ["accept:hook"]
app:
default-policy: ["drop:flow"] # fallback for all app hooks
dns:
default-policy: ["drop:flow"] # fallback for dns hooks
request-started: ["accept:hook"]
request-complete: ["drop:flow", "alert"]
response-started: ["accept:tx"]
Protocols whose hooks are grouped into sub states, such as HTTP/2, take an extra level for the sub state name:
firewall:
policies:
app:
http2:
default-policy: ["drop:flow"] # fallback for all http2 hooks
stream:
default-policy: ["drop:flow"] # fallback for http2 stream hooks
request-started: ["accept:hook"]
global:
request-started: ["accept:hook"]
Precedence:
- packet hook:
packet.<hook>>packet.default-policy>policies.default-policy> built-in (drop:packetoraccept:hook) - app hook:
app.<proto>.<hook>>app.<proto>.default-policy>app.default-policy>policies.default-policy> built-in (drop:flow) - app hook in a sub state:
app.<proto>.<sub state>.<hook>>app.<proto>.<sub state>.default-policy>app.<proto>.default-policy>app.default-policy>policies.default-policy> built-in (drop:flow)
