Firewall Ruleset Examples ========================= .. note:: In Suricata 8 the firewall mode is experimental and subject to change. HTTP ---- In this example a simple HTTP ruleset will be shown. It will allow HTTP to flow as long as: - method is GET or POST - User-Agent is "curl" - Status code is 200. It starts by allowing the TCP port 80 traffic. :: accept:hook tcp:all any any <> any 80 (sid:10;) The stream tracking combined with the default exception policy handling will enforce a proper TCP handshake, etc. The HTTP rules need to ``accept`` each state:: # allow traffic before the request line is complete accept:hook http1:request_started any any -> any any (sid:100;) # allow GET accept:hook http1:request_line any any -> any any ( \ http.method; content:"GET"; sid:101;) # or allow POST accept:hook http1:request_line any any -> any any ( \ http.method; content:"POST"; sid:102;) # allow User-Agent curl accept:hook http1:request_headers any any -> any any ( \ http.user_agent; content:"curl"; sid:103;) # allow the body, if any accept:hook http1:request_body any any -> any any (sid:104;) # allow trailers, if any accept:hook http1:request_trailer any any -> any any (sid:105;) # allow completion accept:hook http1:request_complete any any -> any any (sid:106;) # allow traffic before the response line is complete accept:hook http1:response_started any any -> any any (sid:200;) # allow the 200 ok stat code. accept:hook http1:response_line any any -> any any ( \ http.stat_code; content:"200"; sid:201;) # allow all other states accept:hook http1:response_headers any any -> any any (sid:202;) accept:hook http1:response_body any any -> any any (sid:203;) accept:hook http1:response_trailer any any -> any any (sid:204;) accept:hook http1:response_complete any any -> any any (sid:205;) Each state needs an ``accept`` rule. Each state is evaluated at least once. .. _firewall examples-default policies-http: HTTP example with partially using default policies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In the example below: the config auto accepts various hooks, leaving just ``http1:request_line``, ``http1:request_headers`` and ``http1:response_line`` for the ruleset to accept. :: firewall: policies: app: http1: request-started: - "accept:hook" request-line: - "drop:flow" - "alert" request-headers: - "drop:flow" - "alert" request-body: - "accept:hook" request-trailer: - "accept:hook" request-complete: - "accept:hook" response-started: - "accept:hook" response-line: - "drop:flow" - "alert" response-headers: - "accept:hook" response-body: - "accept:hook" response-trailer: - "accept:hook" response-complete: - "accept:hook" :: # allow GET accept:hook http1:request_line any any -> any any ( \ http.method; content:"GET"; sid:101;) # or allow POST accept:hook http1:request_line any any -> any any ( \ http.method; content:"POST"; sid:102;) # allow User-Agent curl accept:hook http1:request_headers any any -> any any ( \ http.user_agent; content:"curl"; sid:103;) # allow the 200 ok stat code. accept:hook http1:response_line any any -> any any ( \ http.stat_code; content:"200"; sid:201;) TLS SNI with complex TCP rules ------------------------------ In this example the ``packet:filter`` rules will be more opinionated about the traffic:: # allow 3-way handshake accept:hook tcp:all $HOME_NET any -> $EXTERNAL_NET 443 (flags:S; \ flow:not_established; flowbits:set,syn; sid:1;) accept:hook tcp:all $EXTERNAL_NET 443 -> $HOME_NET any (flags:SA; \ flow:not_established; flowbits:isset,syn; flowbits:set,synack; sid:2;) accept:hook tcp:all $HOME_NET any -> $EXTERNAL_NET 443 (flags:A; \ flow:not_established; flowbits:isset,synack; \ flowbits:unset,syn; flowbits:unset,synack; sid:3;) # allow established accept:hook tcp:all $HOME_NET any <> $EXTERNAL_NET 443 (flow:established; sid:4;) Then on the TLS level this will be a TLS SNI firewall. :doc:`tls-state-migration` covers migrating rules and config keys from the pre-phase state names. Again all the states need to be accepted. Only in the ``client_hello`` state will there be additional constraints:: accept:hook tls:client_started $HOME_NET any -> $EXTERNAL_NET any (sid:100;) # allow the good sites accept:flow tls:client_hello $HOME_NET any -> $EXTERNAL_NET any (tls.sni; \ pcre:"/^(suricata.io|oisf.net)$/R"; sid:101;) # drop every ClientHello that carried an SNI the rule above does # not allow; the SNI buffer is required, so a fragment still in # flight (no SNI buffer yet) cannot match drop:flow tls:client_hello $HOME_NET any -> $EXTERNAL_NET any (alert; tls.sni; \ pcre:!"/^(suricata.io|oisf.net)$/R"; sid:102;) # hello packets that carry no verdict of their own: the in-flight # fragments accept:hook tls:client_hello $HOME_NET any -> $EXTERNAL_NET any (sid:103;) # no accept at the certificate state: a hello without an SNI matches # no SNI rule and is decided by the default policy of the states it # walked over (the implicit drop:flow) - the same outcome the base # release gave it at the hello-completion milestone. If you want to # accept SNI-less hellos, add an explicit accept here and audit the # relaxation. accept:hook tls:client_data $HOME_NET any -> $EXTERNAL_NET any (sid:105;) accept:hook tls:client_finished $HOME_NET any -> $EXTERNAL_NET any (sid:106;) accept:hook tls:server_started $EXTERNAL_NET any -> $HOME_NET any (sid:200;) accept:hook tls:server_hello $EXTERNAL_NET any -> $HOME_NET any (sid:201;) accept:hook tls:server_cert $EXTERNAL_NET any -> $HOME_NET any (sid:202;) accept:hook tls:server_data $EXTERNAL_NET any -> $HOME_NET any (sid:204;) accept:hook tls:server_finished $EXTERNAL_NET any -> $HOME_NET any (sid:205;) The ``client_hello`` state names the ClientHello being parsed: it is entered at the first byte of the message, and its completion moves the state to ``client_cert``. The SNI buffer is owned by the hello state, so ``tls.sni`` rules must live in ``client_hello`` even though the record that completes the hello is decided at ``client_cert``: a flow that matches the SNI accept is carried over by the flow-scoped accept (101) and a flow whose SNI is not allowed is dropped by 102 on that same record - the drop is rule-scoped and alerts, where the implicit default policy would drop silently. The plain ``accept:hook`` at 103 carries the fragments in flight (the SNI buffer is not available before the message completes, so it also cannot gate them). A ClientHello that carries no SNI at all matches no SNI rule either - and with no accept at the certificate state it is decided by the default policy of the states it walked over (the implicit drop:flow), the same outcome the base release gave it at the hello-completion milestone. A flow whose hello is accepted by 101 is carried by the flow-scoped accept: the flow action is checked before any further firewall rule of a packet is evaluated, so the later states of that flow never reach the implicit default. ``client_started`` covers the packets before the hello. TLS SNI with auto-accept logic ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Rule that has the same effect as the 11 TLS rules above:: accept:flow tls: $EXTERNAL_NET any (tls.sni; \ pcre:"/^(suricata.io|oisf.net)$/R"; sid:101;) Explanation: ``accept:flow`` accepts all of the TLS flow from the moment the rule has matched. The ``tls:client_started`` hook is auto-accepted by the use of the ``<`` modifier in the hook ``tls: $EXTERNAL_NET any \ (tls.sni; pcre:"/^(suricata.io|oisf.net)$/; sid:101;) Explanation: as soon as this rule fully matches at the ``tls:client_hello`` hook, a ``pass`` is applied to the flow effectively bypassing the threat detection engine.