Skip to content

XFRM interfaces with VRF-based multitenant IPsec

This example configures a multitenant IPsec setup:

  • use strongSwan for IPsec setup
  • use VLAN subinterfaces for inside VRF access
  • XFRM interfaces to connect IPsec tunnels with the VRFs (requires Linux kernel 4.19+)
interfaces:
  # external interface for IPsec termination
  outside:
    addresses:
      - 198.51.100.2/31
    link:
      state: up
      kind: physical
    identify:
      perm_address: 00:50:56:ad:db:ac

  # inside base interface
  trunk:
    link:
      kind: physical
      state: up
    identify:
      perm_address: 8c:16:45:dc:b1:ad


  # first tenant VRF
  vrf-tenant1:
    link:
      state: up
      kind: vrf
      vrf_table: 101

  ipsec-tenant1:
    link:
      state: up
      kind: xfrm
      xfrm_link: outside
      xfrm_if_id: 1
      master: vrf-tenant1

  inside-tenant1:
    addresses:
      - 192.0.2.1/24
    link:
      state: up
      kind: vlan
      link: trunk
      vlan_id: 41
      master: vrf-tenant1


    # second tenant VRF
    vrf-tenant2:
      link:
        state: up
        kind: vrf
        vrf_table: 102

    ipsec-tenant2:
      link:
        state: up
        kind: xfrm
        xfrm_link: outside
        xfrm_if_id: 2
        master: vrf-tenant2

    inside-tenant2:
      addresses:
        - 192.0.2.1/24
      link:
        state: up
        kind: vlan
        link: trunk
        vlan_id: 42
        master: vrf-tenant2


routing:
    routes:
      # outside default route
      - to: 0.0.0.0/0
        via: 198.51.100.1

      # first tenant VRF: add default route into vpn
      - to: 0.0.0.0/0
        dev: ipsec-tenant1
        table: 101

      # second tenant VRF: add default route into vpn
      - to: 0.0.0.0/0
        dev: ipsec-tenant2
        table: 102
{
  networking.ifstate = {
    enable = true;
    settings = {
      interfaces = {
        # external interface for IPsec termination
        outside = {
          addresses = [ "198.51.100.2/31" ];
          link = {
            state = "up";
            kind = "physical";
          };
          identify.perm_address = "00:50:56:ad:db:ac";
        };

        # inside base interface
        trunk = {
          link = {
            kind = "physical";
            state = "up";
          };
          identify.perm_address = "8c:16:45:dc:b1:ad";
        };


        # first tenant VRF
        vrf-tenant1 = {
          link = {
            state = "up";
            kind = "vrf";
            vrf_table = 101;
          };
        };
        ipsec-tenant1 = {
          link = {
            state = "up";
            kind = "xfrm";
            xfrm_link = "outside";
            xfrm_if_id = 1;
            master = "vrf-tenant1";
          };
        };
        inside-tenant1 = {
          addresses = [ "192.0.2.1/24" ];
          link = {
            state = "up";
            kind = "vlan";
            link = "trunk";
            vlan_id = 41;
            master = "vrf-tenant1";
          };

          # second tenant VRF
          vrf-tenant2 = {
            link = {
              state = "up";
              kind = "vrf";
              vrf_table = 102;
            };
          };
          ipsec-tenant2 = {
            link = {
              state = "up";
              kind = "xfrm";
              xfrm_link = "outside";
              xfrm_if_id = 2;
              master = "vrf-tenant2";
            };
          };
          inside-tenant2 = {
            addresses = [ "192.0.2.1/24" ];
            link = {
              state = "up";
              kind = "vlan";
              link = "trunk";
              vlan_id = 42;
              master = "vrf-tenant2";
            };
          };
        };
      };
      routing = {
        routes = [
          # outside default route
          {
            to = "0.0.0.0/0";
            via = "198.51.100.1";
          }

          # first tenant VRF: add default route into vpn
          {
            to = "0.0.0.0/0";
            dev = "ipsec-tenant1";
            table = 101;
          }

          # second tenant VRF: add default route into vpn
          {
            to = "0.0.0.0/0";
            dev = "ipsec-tenant2";
            table = 102;
          }
        ];
      };
    };
  };
}

strongSwan

To make strongSwan VRF aware you must use swanctl. It is not possible to use the classic ipsec.conf with XFRM interfaces. A swanctl configuration might look like:

#
# IPsec tunnel for tenant1
#
connections {
    # Section for an IKE connection named <conn>.
    tentant1 {
        # IKE major version to use for connection.
        version = 2

        # Local address(es) to use for IKE communication, comma separated.
        local_addrs = 198.51.100.2

        # Remote address(es) to use for IKE communication, comma separated.
        remote_addrs = 203.0.113.1

        # Default inbound XFRM interface ID for children.
        if_id_in = 1

        # Default outbound XFRM interface ID for children.
        if_id_out = 1

        # Section for a local authentication round.
        local {
            auth = psk
        }

        # Section for a remote authentication round.
        remote {
            auth = psk
        }

        children {
            # CHILD_SA configuration sub-section.
            tenant1 {
                # Local traffic selectors to include in CHILD_SA.
                local_ts = 192.0.2.0/24

                # Remote selectors to include in CHILD_SA.
                remote_ts = 0.0.0.0/0
            }
        }
    }
}


#
# IPsec tunnel for tenant2
#
connections {
    # Section for an IKE connection named <conn>.
    tentant2 {
        # IKE major version to use for connection.
        version = 2

        # Local address(es) to use for IKE communication, comma separated.
        local_addrs = 198.51.100.2

        # Remote address(es) to use for IKE communication, comma separated.
        remote_addrs = 203.0.113.1

        # Default inbound XFRM interface ID for children.
        if_id_in = 2

        # Default outbound XFRM interface ID for children.
        if_id_out = 2

        # Section for a local authentication round.
        local {
            auth = psk
        }

        # Section for a remote authentication round.
        remote {
            auth = psk
        }

        children {
            # CHILD_SA configuration sub-section.
            tenant1 {
                # Local traffic selectors to include in CHILD_SA.
                local_ts = 192.0.2.0/24

                # Remote selectors to include in CHILD_SA.
                remote_ts = 0.0.0.0/0
            }
        }
    }
}