Difference between revisions of "VPP/SecurityGroups"

From fd.io
< VPP
Jump to: navigation, search
(Existing functionality)
(Revised API)
Line 80: Line 80:
 
   u8 is_permit;
 
   u8 is_permit;
 
   u8 is_ipv6;
 
   u8 is_ipv6;
  u8 src_l2_address[6]
 
  /* src_l2_mask[6] or u8 use_src_l2_address ? */
 
  u8 dst_l2_address[6]
 
  /* dst_l2_mask[6] or u8 use_dst_l2_address ? */
 
 
   u8 src_ip_addr[16];
 
   u8 src_ip_addr[16];
 
   u8 src_ip_prefix_len;
 
   u8 src_ip_prefix_len;

Revision as of 18:26, 13 October 2016

VPP Security Groups

Introduction

Features are tracked as they are developed in the following VPP-427.

Requirements

  • Support classifiers/filters on any interface type (bridged / routed)
  • Filter on IP-addresses with address mask or prefix length (IPv4 and IPv6)
  • Filter on source and destination TCP/UDP port ranges
  • Filter on source and destination L2 MAC addresses
  • Support IPv6 with extension headers present
  • Support fragmented packets and unknown transport layer headers
  • Combinations of the above filters (e.g. MAC + IP)
  • Filters on ingress and egress interfaces
  • Stateful firewall. No application layer filtering.

Work list

Task Owner Priority Status Description
API definition Ole 0 WIP
Ingress/Egress support for classifier 0
Support for L2/L3 interfaces 0
"Established" behaviour 1
Stateful firewall 1
Port ip_tables_firewall.py from Neutron as unit test 1

Revised API

/*
 * Access List Rule entry
 *
 * Future considerations:
 * u32 proto_flags;
 * u8 traffic_class;
 * u32 flow_label;
 * u32 extension_header_present;
 * u8 port_range_operator
 */
typeonly define acl_rule
{
 u8 is_permit;
 u8 is_ipv6;
 u8 src_ip_addr[16];
 u8 src_ip_prefix_len;
 u8 dst_ip_addr[16];
 u8 dst_ip_prefix_len;
 u8 proto;
 u16 src_min_port;
 u16 src_max_port;
 u16 dst_min_port;
 u16 dst_max_port;
};
define acl_add
{
 u32 client_index;
 u32 context;
 u32 count;
 vl_api_acl_rule r[count];
};
define acl_add_reply
{
 u32 context;
 u32 acl_index
 i32 retval;
};
define acl_del
{
 u32 client_index;
 u32 context;
 u32 acl_index
};
define acl_del_reply
{
 u32 context;
 i32 retval;
};
define acl_interface_add_del
{
 u32 client_index;
 u32 context;
 u8 is_add;
 u8 is_input;
 u32 sw_if_index;
 u32 acl_index;
}
define acl_interface_add_del_reply
{
 u32 context;
 i32 retval;
};
define acl_dump
{
 u32 client_context;
 u32 context;
 u32 sw_if_index; /* ~0 for all tunnels */
}
define acl_details
{
 u32 context;
 u32 sw_if_index;
 u32 acl_index;
 u32 count;
 vl_api_acl_rule_t r[count];
}

API

There is no API to set the default policy - this behavior can be achieved by adding a wildcard (all zero) protocol/ports/address, and may be handled internally as a special case. Nonetheless, there is no explicit API for setting this. Between the overlapping rules with different actions, the more specific rules always match first. This is done to ensure the matching is order-independent, as well as to allow the flexibility in the internal implementation.

/**
  Add or delete egress IP access policy rule. 
  The egress rule (*to* host(s) attached to this particular interface) 
  allows/denies the sessions to be initiated *to* the services running on this host(s).   
  */
 
define ip_apr_add_del_egress
{
       u32 client_index;
       u32 context;
       u32 sw_if_index;
       u8 is_add
       u8 allow;
       u8 is_ipv6;
       u8 src_ip_addr[16];
       u8 src_ip_prefix_len;
       u8 proto;
       u16 dst_min_port;
       u16 dst_max_port;
};
  
   
define ip_apr_add_del_egress_reply
{
       u32 context;
       i32 retval;
};
  
  
  
/**
  Add or delete ingress IP access policy rule. The ingress rule (*from* host(s) attached 
  to this particular interface) allows/denies the sessions to be initiated 
  *from* the host(s) attached to this interface.   
 */
  
define ip_apr_add_del_ingress
{
       u32 client_index;
       u32 context;
       u32 sw_if_index;
       u8 is_add;
       u8 allow;
       u8 is_ipv6;
       u8 dst_ip_addr[16];
       u8 dst_ip_prefix_len;
       u8 proto;
       u16 dst_min_port;
       u16 dst_max_port;
};
  
   
define ip_apr_add_del_ingress_reply
{
       u32 context;
       i32 retval;
};
  
  
/**
  Add or delete MAC / IP ingress filter. 
  These rules restrict the MAC addresses that can send the traffic. 
  If the ip_address is all-zero, any IP address is allowed and only
  the MAC address is used for the ingress filtering.
  There can be many MAC addresses on a given interface,
  a given MAC address may have multiple addresses associated with it 
  (by means of separate ingress rules), and different MAC addresses can also have the same addresses.  
*/
  
  
define ip_apr_macip_add_del_ingress
{
       u32 client_index;
       u32 context;
       u32 sw_if_index;
       u8 is_add;
       u8 is_ipv6;
       u8 mac_address[6];
       u8 ip_address[16];
};
  
/** 
  @param context            - sender context, to match reply w/response
  @param retval             - return code for the request
*/
  
define ip_apr_macip_add_del_ingress_reply
{
       u32 context;
       i32 retval;
};

General approach to the design

This functionality will be implemented using the "fastpath"/"slowpath" concept, with a few twists which are outlined later on.

On ingress, the packet will perform a lookup in a table matching the (MAC, IP) bindings, if they are configured. If there is no match in that table, the packet is dropped.

After that, a lookup will be done in a session table. (NB: whether we use classifier for this or not, is an open question, due to the existence of the IPv6 extension headers). If there is a match in the session table with a regular session entry, then potentially the breadcrumb information in the packet can set about its anticipated egress interface for later verification, and the packet processing continues as normal.

If there is a match in the session table with a drop-session entry, then the packet is dropped and a counter is incremented.

If there is no match in the session table, then the ingress security policy lookup is performed.

We have determined that in the generic case the security policy database may not be a classifier. How to implement it is TBD.

If the policy denies the packet, the packet is dropped, and potentially a drop-session is created.

If there policy permits the packet, then a breadcrumb "first packet" is set in the packet indicating this, and the packet processing continues as normal.

On egress, a check of a breadcrumb in the packet is performed:

breadcrumb is an egress interface -> we can verify the egress interface is as anticipated and either permit+log or drop the packet if it does not match.

breadcrumb is a "first packet" -> we perform a lookup in the egress policy on the interface.

If the policy permits the packet: we create a session in the session table on the ingress interface, noting there the egress interface, and send the packet on.

If the policy denies the packet: we drop the packet and optionally create a drop-session on the ingress interface.

From this description it can be seen that "slow path" and "fast path" are a bit of misnomers - the only "fast" about the fast path is that the packets there skip the policy check. Such an approach allows to minimize the discrepancies in forwarding of the initial and subsequent packets and avoid the creation of a second forwarding path.


Implementation plan

The high-level plan is to first create the stateless slow-path-only functionality and then add the fast-path stateful component. This implies that the slow-path can classify the return packets as well. While this is not precisely the same behavior as with stateful filter, having this was deemed useful both for development purposes to stage the development and as a potentially practical option for later. There will be a knob to allow the additional "mirrored" lookup, where the source and destination of the packets are swapped (and certain other additions will apply, e.g. the return packets for the TCP session can not be pure SYNs)

CLI

set interface input acl intfc <int> [ip4-table <index>] [ip6-table <index>] [l2-table <index>] [del] 
show inacl type [ip4|ip6|l2]
classify table [miss-next|l2-miss_next|acl-miss-next <next_index>] mask <mask-value> buckets <nn> [skip <n>] [match <n>] [del]
show classify tables [index <nn>]
classify session [hit-next|l2-hit-next|acl-hit-next <next_index>|policer-hit-next <policer_name>] table-index <nn> match [hex] [l2] [l3 ip4] [opaque-index <index>]
test classify [src <ip>] [sessions <nn>] [buckets <nn>] [table <nn>] [del]
set ip classify intfc <int> table-index <index>
set interface ip6 table <intfc> <table-id>
set interface l2 input classify intfc <interface-name> [ip4-table <n>] [ip6-table <n>] [other-table <n>]
set interface l2 output classify intfc <<interface-name>> [ip4-table <n>] [ip6-table <n>] [other-table <n>]
set ip source-and-port-range-check
show ip source-and-port-range-check vrf <nn> <ip-addr> <port>

Examples

YANG model

Open Issues

  • Security Group use case specific API. Done in VPP or control plane plugin?

Existing functionality

The existing functionality has a classifier (https://wiki.fd.io/view/VPP/Introduction_To_N-tuple_Classifiers) matching.

As the above document explains, the classifier is a series of chained tables, with each table having a specific mask, but this mask is the same for all entries.

This has been tested to happen in the L2 bridged case (test case: http://stdio.be/vpp/t/aytest-bridge-tap-py.txt).

Therefore, if we have an example policy:

 nova secgroup-create test-secgroup test
 nova secgroup-add-rule test-secgroup icmp -1 -1 0.0.0.0/0
 nova secgroup-add-rule test-secgroup tcp 22 22 0.0.0.0/0

So, assuming we match with offset 0 (from the beginning of the packet) the mask will look like this for the first line:

 000000000000 000000000000 0000 00 00 0000 0000 0000 00 FF 0000 00000000 00000000  00 00 0000 0000 
   eth dst      eth src    et   ihl t  len id    fo ttl pr  cs   ip4src   ip4dst    t  c  cs   id
   +-------- L2 ---------------+----------- L3 IPv4 ------------------------------+--------L4 ICMP -----+

For the TCP matching on port 22 it will look as follows:

 000000000000 000000000000 0000 00 00 0000 0000 0000 00 FF 0000 00000000 00000000  0000 FFFF 00000000 00000000 0000 0000 0000 0000
   eth dst      eth src    et   ihl t  len id    fo ttl pr  cs   ip4src   ip4dst    sp  dp    seq      ack      fl  win   cs   urg
   +-------- L2 ---------------+----------- L3 IPv4 ------------------------------+--------L4 TCP ---------------------------------+


(One would need to round up the number of bytes to the nearest 16-byte boundary that makes sense)

For IPv6 assuming no extension headers, it will look similar, with the L3 header being the IPv6 one:


 000000000000 000000000000 0000 0 00 00000 0000 FF 00 00000000000000000000000000000000 00000000000000000000000000000000 00 00 0000 0000 
   eth dst      eth src    et   v TC  fll  len  nh hl             ipv6 src                   ipv dst                    t  c  cs   id
   +-------- L2 ---------------+----------- L3 IPv6 --------------------------------------------------------------------+--------L4 ICMP -----+

For the TCP matching on port 22 it will look as follows:

 000000000000 000000000000 0000 0 00 00000 0000 FF 00 00000000000000000000000000000000 00000000000000000000000000000000 0000 FFFF 00000000 00000000 0000 0000 0000 0000
   eth dst      eth src    et   v TC  fll  len  nh hl             ipv6 src                   ipv dst                      sp  dp    seq      ack      fl  win   cs   urg
   +-------- L2 ---------------+----------- L3 IPv6 --------------------------------------------------------------------+--------L4 TCP ---------------------------------


Then using these masks one would create 4 tables, by using the API call:

 classify_add_del_table(is_add=1, skip_n_vectors=0, mask=<MMMM>, match_n_vectors=<NNNN>,nbuckets=32,memory_size=20000, next_table_index=-1, miss_next_index=-1)

Let's call these tables "IPv4PROTO", "IPv4PROTO_TCPDPORT", "IPv6PROTO", "IPv6PROTO_TCPDPORT".

One would mention "IPv4PROTO" table as "next_table_index" table for "IPv4PROTO_TCPDPORT", and "IPv6PROTO" as "next_table_index" table for IPv6PROTO_TCPDPORT table.

Then one needs to populate the tables with the correct matches for "ICMP" and "tcp dst port 22". That can be done using API call:

 classify_add_del_session(is_add=1, table_index=<XXXX>, match=<bytes-to-match>, hit-next-index -1)

The bytes "XXXX" above would be the match of one or several vectors, corresponding to the packet contents with the desired value.

Then one would apply the IPv4PROTO_TCPDPORT and IPv6PROTO_TCPDPORT as l2 input classify tables.

The CLI for that is set interface l2 output classify intfc <name> ip[46]-table <tableid>.

The API for this is

  classify_set_interface_l2_tables(sw_if_index=<INTFC>, ip4_table_index=<IPv4PROTO_TCPDPORT>, ip6_table_index=<IPv6PROTO_TCPDPORT>, other_table_index=-1, is_input=0)


This would allow to create a unidirectional policy, assuming the other policy is "permit all" it would be fine. If not - then a mirror table entries will need to be created using the same logic.

The Java API is located in $ROOT/vpp-api/java..

References