Gateway Peering ACLs
A Gateway Peering can carry an Access Control List
(ACL): an ordered list of rules that decides, for each new flow or packet,
whether the gateway admits it or drops it. ACLs are configured via the
optional, peering-scoped spec.acl field.
ACLs are opt-in: a peering with no spec.acl behaves exactly as described
in Gateway Peering.
The full list of fields is in the Fabric & Gateway APIs reference.
Traffic filtering in Gateway Peerings
Several mechanisms restrict what traffic can flow between the two VPCs of a
Gateway Peering. Before any ACL rule is evaluated, the first mechanism is the
definition of the peering itself, via the prefixes in the expose blocks: when
the gateway receives a packet, it attempts to determine its destination VPC by
finding the right peering based on the source VPC, source IP and port,
destination IP and port (ports are used in the case of port-forwarding). If a
packet does not match prefixes from exposed blocks in any peering attached to
its source VPC, it is dropped.
Some stateful NAT modes, such as masquerade and port-forwarding, add a second restriction: a flow may only be initiated in one direction. When masquerade is set-up, a flow can only be initiated from the VPC with the masqueraded prefixes; a packet sent towards a masqueraded host will be dropped if the gateway has no associated stateful flow entry in its table, even if the address is otherwise valid with regards to the peering definition. Conversely, flows can only be initiated towards, and never from, an endpoint using port-forwarding.
ACL rules come as a third mechanism, and add explicit allow/deny decisions based on 5-tuples. Traffic restrictions from ACL rules come in addition to the restrictions implied by the Gateway Peering definition. In other words, an ACL rule will never allow any packet that is not permitted by the prefixes exposed in the peering configuration.
ACL configuration
To configure an ACL, use field spec.acl alongside spec.peering. The ACL
applies to the whole peering. The YAML definition for the expose blocks
remains unchanged. Here is an example:
This peering lets hosts in prefix 10.0.1.0/24 from vpc-app open TCP
connections on port 5432 to hosts with prefix 10.0.2.0/24 from vpc-db,
allows return traffic to go through as well, and nothing else (default: deny)
in either direction.
Rule evaluation order
The gateway evaluates rules in spec.acl.rules top-down: the first rule
whose from/to and match pattern match the flow decides the verdict, and
evaluation stops immediately. If no rule matches, spec.acl.default applies.
Conflicts between rules are resolved purely by order, so a broad rule placed
early will shadow the more specific rules that follow it.
Rule direction: from and to
Every rule specifies a direction. Fields from and to must each
name one of the peering's two sides, using the same names as the keys under
spec.peering (externals included, prefixed with ext.). A rule applies to
packets emitted by from towards to.
Because a peering always has exactly two sides, one of from or to may be
omitted, and the other side is implied. Omitting both is not allowed.
Depending on the scope specified for the rule, rules may also apply to
packets of an established flow's reverse direction. Refer to the section on
rule scope for details.
Matching patterns
The match block defines what packets (or flows) an ACL rule applies to.
NAT considerations
The gateway evaluates ACL rules for a packet before any NAT is applied.
This means that the patterns in the match block are compared with the real
source address and the destination address the initiator used (typically,
its exposed service address).
For example, let's consider vpc-01 exposing 10.0.1.0/24 (without NAT), and
vpc-02 exposing a private address 10.0.2.10 behind port-forwarding, as
public address 100.64.2.10/32:
-
To write a rule to allow endpoint
10.0.1.1onvpc-01to reach a server exposed as a service behind100.64.2.10onvpc-02, there is no need to account for the translated address. As ACL rules are evaluated before NAT (in this case, port-forwarding) takes place, the destination address100.64.2.10is used to write the rule, regardless of what private address invpc-02the gateway translates it to. -
To write a rule to forbid the server on
vpc-02to talk to some endpoint (say,10.0.1.4) onvpc-01, the same applies, in the opposite direction: the ACL rule evaluation comes before NAT, which means we use the private IP address for the server,10.0.2.10, and the plain destination address of the endpoint at10.0.1.4, to write the match pattern (although in the current case, the server is already forbidden to initiate connections by the peering definition, because it is exposed using port-forwarding).
Match block
The match block is a 5-tuple, expressed from the initiator's point of view:
srccontains thefromside's native address prefixes and portsdstcontains thetoside's advertised address prefixes and ports (from itsexpose'sas, oripswhere no NAT applies)protocan betcp,udp, or a quoted protocol number (for example:"132"for SCTP)
src and dst are lists of
match endpoints. Each
entry carries an IP prefix and an optional list of ports, where a port entry is
either a single port such as "80" (do not omit the quotes) or an inclusive
range such as "8000-8100". The prefix takes the same shape as an expose
entry: either a literal cidr, or a vpcSubnet reference naming a VPC subnet.
At most one of cidr and vpcSubnet can be set on a given entry.
Here are some match block examples:
- Match all UDP traffic towards hosts in prefix
10.0.2.0/24:
- Match all TCP traffic with destination port 25, for all exposed IP addresses on the relevant side of the peering:
- Match all TCP traffic from prefixes
10.0.1.0/24and10.0.2.0/24, to prefix10.0.5.0/24with destination port 5432:
match:
src:
- cidr: 10.0.1.0/24
- cidr: 10.0.2.0/24
dst:
- cidr: 10.0.5.0/24
ports: ["5432"]
proto: tcp
- Match UDP traffic from IP address
10.0.2.12with source port between 8000 and 8100 inclusive, or equal to 9000, to any valid destination defined by the peering:
- Match all traffic between
10.0.1.1and10.0.2.1, regardless of protocol and ports:
- Match all TCP traffic in the rule's direction. Non-TCP traffic does not match the rule.
- Match SCTP traffic from
10.0.1.1to10.0.2.1:
Note that source and destination ports can only be used when the proto field
is set to tcp or udp.
Tip
Field proto supports values tcp, udp, or a quoted protocol number. An
icmp keyword should be added in the future, with related matching options
such as error types; until then, you can use protocol number "1" to match
ICMP packets.
Optional match fields
Everything in match is optional, and omitting a field widens the rule:
| Field | If omitted, then... |
|---|---|
src, dst |
the rule matches any source or destination address and port within the peering configuration, respectively |
src.{cidr,vpcSubnet}, dst.{cidr,vpcSubnet} |
the rule matches any source or destination IP address within the peering configuration (respectively), but can still be restricted to specified port ranges |
src.ports, dst.ports |
the rule matches all source or destination ports within the peering configuration, respectively |
proto |
the rule matches any protocol |
If the match block is omitted altogether, the rule matches every flow in the
specified direction: this is how to write a generic allow or deny for one
direction.
Rule scope
The scope field in each rule selects whether the rule is stateful:
flow(the default) makes the rule bidirectional for established connections. Once a flow is admitted, its return traffic is matched by the flow table entry and allowed even if it does not match any other explicit allow rule. The connection can still only be initiated in the rule's direction.packetmakes the rule stateless. Return traffic is not implicitly allowed and must be permitted by a separate rule.
Note that under default: deny, a rule with scope: packet and no matching
reverse rule admits the forward direction while dropping every reply. Add
an explicit rule for the return direction whenever you use scope: packet and
need bidirectional traffic.
Also, note that reply traffic for allow rules with scope: flow may still be
dropped if it matches an explicit deny rule in the list: packets are evaluated
as potential "return traffic" only after going through a first direct lookup
against the ACL rules table.
The scope does not matter in the case of deny rules, given that no flow
matching this rule can be initiated in the first place.
Warning
Due to internal limitations in the gateway, scope: flow is currently
restricted to peerings for which all flows use masquerade or
port-forwarding. Flows relying on static NAT, or no NAT at all, do not
currently generate stateful entries in the gateway's table. This limitation
will be lifted in a future release.
Default action
When no rule matches, the default verdict from spec.acl.default is
applied. The field can take one of two values:
-
deny-unless-exposed(the default) denies the packet unless it corresponds to a defined peering. This means that the packet is rejected unless it matches the following conditions:- Its source VPC, source IP address, and destination address match exposed prefixes (and source or destination ports match exposed port ranges, in the case of port-forwarding) for a peering attached to the source VPC.
- If the packet attempts to establish a new flow, the NAT modes in use —
as defined in the relevant
exposeblocks for the peering — do not prevent it. - No deny rule in the ACL matches the packet.
See also the section about
traffic filtering in Gateway Peerings.
If a packet matches all these conditions, then deny-unless-exposed allows
it to go through.
denymeans that packets not explicitly allowed by a rule (or corresponding to return traffic for an established flow allowed by a rule withscope: flow) are dropped. So even a packet that matches the peering is dropped if no allow rule matches it.
Logging
Set the log field of a rule to true to log all packets matching that rule
(as well as corresponding reply traffic, for rules with scope: flow). The
rule's name, if specified, appears in the log message.
Examples
This section contains some example use cases and configuration for the peering ACLs.
Restricting an exposed service
VPC vpc-02 only publishes a backend as 100.64.2.10:443.
A flow from 10.0.1.5 to 100.64.2.10:443 is evaluated before NAT, so the
rule matches vpc-02's backend address before port-forwarding takes place.
Based on the scope, reply traffic is automatically allowed.
Egress allow-list towards an external
A private subnet reaches the Internet through masquerade, with a strict list of allowed destination ports, and one carve-out rule placed ahead of it.
Match field src is omitted throughout, so it matches any address
vpc-internal exposes through the peering. The deny rule comes first because
the first match decides the verdict: moving allow-web above
block-blackholed-ranges would allow HTTP/HTTPS towards the blackholed ranges.
Bidirectional traffic with static NAT
In the following setup, vpc-01 exposes a prefix using static NAT, and uses an
ACL to restrict communications to a few hosts within the prefix. Rules apply
per packet and not per flow (scope: packet), meaning that the return traffic
has to be explicitly allowed.
Endpoint 10.0.2.28 from vpc-02 can communicate with 10.0.1.10 from
vpc-01 both ways, thanks to the first rule allowing traffic from 10.0.1.10;
however, endpoint 10.0.2.12 from vpc-02 can emit towards 10.0.1.10, but
will not receive any return traffic.
Blocking sub-ranges
Here is a peering where vpc-01 only exposes portions of subnet 10.0.1.0/24
to vpc-02. This example illustrates how ACL rules can provide finer-grained
control than the expose block: it offers control over direction, protocol,
and port ranges.
With this configuration, when vpc-01 sends packets towards vpc-02:
- All packets that do not come from
10.0.1.0/24, or are not addressed to10.0.2.0/24, are dropped, because they do not match the peering definition. - From
10.0.1.0/26: Packets match the peering and do not match any deny rule, sodeny-unless-exposedlets them pass. - From
10.0.1.64/26: Per the peering definition, this sub-prefix is not part of the exposed block (not:), so packets are dropped. - From
10.0.1.128/26: Because of the first ACL rule, packets are dropped. - From
10.0.1.192/27:- TCP traffic is dropped for source ports between 2000 and 3000 (inclusive), because of the second rule.
- TCP traffic from other ports is allowed by
deny-unless-exposed. - Non-TCP traffic is allowed by
deny-unless-exposed.
- From
10.0.1.224/27: Packets match the peering and do not match any deny rule, sodeny-unless-exposedlets them pass.
And when vpc-02 sends packets towards vpc-01:
- All packets that do not come from
10.0.2.0/24, or are not addressed to10.0.1.0/24, are dropped, because they do not match the peering definition. - To
10.0.1.0/26: Packets match the peering and do not match any deny rule, sodeny-unless-exposedlets them pass. - To
10.0.1.64/26: Per the peering definition, this sub-prefix is not part of the exposed block (not:), so packets are dropped. - To
10.0.1.234:- UDP traffic to port 53 is dropped, because of the third rule.
- UDP traffic to other ports is allowed by
deny-unless-exposed. - Non-UDP traffic is allowed by
deny-unless-exposed.
- To other addresses in
10.0.1.0/24: Packets match the peering and do not match any deny rule — the first two rules in the list do not apply to traffic in this direction — sodeny-unless-exposedlets them pass.