Message ID | 20180627175844.2809-2-qiuyu.xiao.qyx@gmail.com |
---|---|
State | Changes Requested |
Headers | show |
Series | IPsec support for tunneling | expand |
Bleep bloop. Greetings Qiuyu Xiao, I am a robot and I have tried out your patch. Thanks for your contribution. I encountered some error that I wasn't expecting. See the details below. checkpatch: WARNING: Line is 85 characters long (recommended limit is 79) #372 FILE: debian/openvswitch-ipsec.init:27: # Description: The ovs-monitor-ipsec script provides support for encrypting GRE WARNING: Line is 80 characters long (recommended limit is 79) #524 FILE: debian/openvswitch-ipsec.init:179: log_warning_msg "Reloading $NAME daemon: not implemented, as the daemon" WARNING: Line is 80 characters long (recommended limit is 79) #529 FILE: debian/openvswitch-ipsec.init:184: echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2 WARNING: Line is 81 characters long (recommended limit is 79) #679 FILE: ipsec/ovs-monitor-ipsec:116: if len(a) >= 4 and a[0] == "sel" and a[1] == "src" and a[3] == "dst": WARNING: Line is 80 characters long (recommended limit is 79) #989 FILE: ipsec/ovs-monitor-ipsec:426: self.CHARON_CONF = strongswan_root_prefix + "/etc/strongswan.d/ovs.conf" WARNING: Line is 94 characters long (recommended limit is 79) #1066 FILE: ipsec/ovs-monitor-ipsec:503: ipsec_conf.write(self.SHUNT_POLICY % (self.in_use_skb_mark, self.in_use_skb_mark)) WARNING: Line is 82 characters long (recommended limit is 79) #1259 FILE: ipsec/ovs-monitor-ipsec:696: seqno = idl.change_seqno # Sequence number when OVSDB was processed last time Lines checked: 2515, Warnings: 7, Errors: 0 Please check this out. If you feel there has been an error, please email aconole@bytheb.org Thanks, 0-day Robot
Thanks for the patch. It's really cool to see IPSec being re-integrated. Just a few quick comments inline. Qiuyu Xiao <qiuyu.xiao.qyx@gmail.com> writes: > From: Ansis Atteka <aatteka@ovn.org> > > This patch reintroduces ovs-monitor-ipsec daemon that > was previously removed by commit 2b02d770 ("openvswitch: > Allow external IPsec tunnel management.") > > The reason for removal at the time was that there were IPsec > flavoured tunnel types, like ipsec_gre, that consumed for > themselves the least significant bit of SKB mark irrelevant > whether Open vSwitch users were using IPsec or not. > > After this patch, there are no IPsec flavored tunnels anymore. > IPsec is enabled by setting up the right values in: > 1. OVSDB:Interface:options column; > 2. OVSDB:Open_vSwitch:other_config column; > 3. OpenFlow pipeline. > > Signed-off-by: Ansis Atteka <aatteka@ovn.org> > --- Have you looked into what it would take to support other systems (for instance, BSDs, Fedora/RHEL, CentOS)? > Documentation/automake.mk | 1 + > Documentation/howto/index.rst | 1 + > Documentation/howto/ipsec.rst | 199 ++++++ > Makefile.am | 1 + > debian/automake.mk | 3 + > debian/control | 21 + > debian/openvswitch-ipsec.dirs | 1 + > debian/openvswitch-ipsec.init | 189 ++++++ > debian/openvswitch-ipsec.install | 1 + > ipsec/automake.mk | 9 + > ipsec/ovs-monitor-ipsec | 727 ++++++++++++++++++++ > tests/automake.mk | 1 + > tests/ovs-monitor-ipsec.at | 1076 ++++++++++++++++++++++++++++++ > tests/testsuite.at | 1 + > vswitchd/vswitch.xml | 83 ++- > 15 files changed, 2310 insertions(+), 4 deletions(-) > create mode 100644 Documentation/howto/ipsec.rst > create mode 100644 debian/openvswitch-ipsec.dirs > create mode 100644 debian/openvswitch-ipsec.init > create mode 100644 debian/openvswitch-ipsec.install > create mode 100644 ipsec/automake.mk > create mode 100755 ipsec/ovs-monitor-ipsec > create mode 100644 tests/ovs-monitor-ipsec.at > > diff --git a/Documentation/automake.mk b/Documentation/automake.mk > index 2202df45b..3a505924a 100644 > --- a/Documentation/automake.mk > +++ b/Documentation/automake.mk > @@ -59,6 +59,7 @@ DOC_SOURCE = \ > Documentation/howto/docker.rst \ > Documentation/howto/dpdk.rst \ > Documentation/howto/firewalld.rst \ > + Documentation/howto/ipsec.rst \ > Documentation/howto/kvm.rst \ > Documentation/howto/libvirt.rst \ > Documentation/howto/selinux.rst \ > diff --git a/Documentation/howto/index.rst b/Documentation/howto/index.rst > index 201d6936b..9a3487be3 100644 > --- a/Documentation/howto/index.rst > +++ b/Documentation/howto/index.rst > @@ -37,6 +37,7 @@ OVS > :maxdepth: 2 > > kvm > + ipsec > selinux > libvirt > ssl > diff --git a/Documentation/howto/ipsec.rst b/Documentation/howto/ipsec.rst > new file mode 100644 > index 000000000..4e4f4d211 > --- /dev/null > +++ b/Documentation/howto/ipsec.rst > @@ -0,0 +1,199 @@ > +.. > + Licensed under the Apache License, Version 2.0 (the "License"); you may > + not use this file except in compliance with the License. You may obtain > + a copy of the License at > + > + http://www.apache.org/licenses/LICENSE-2.0 > + > + Unless required by applicable law or agreed to in writing, software > + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT > + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the > + License for the specific language governing permissions and limitations > + under the License. > + > + Convention for heading levels in Open vSwitch documentation: > + > + ======= Heading 0 (reserved for the title in a document) > + ------- Heading 1 > + ~~~~~~~ Heading 2 > + +++++++ Heading 3 > + ''''''' Heading 4 > + > + Avoid deeper levels because they do not render well. > + > +============================================== > +How to Encrypt Open vSwitch Tunnels with IPsec > +============================================== > + > +This document describes how to use Open vSwitch integration with strongSwan > +5.1 or later to provide IPsec security for STT, GENEVE, GRE and VXLAN tunnels. > +This document assumes that you have already installed Open vSwitch. > + > + > +Limitations > +----------- > + > +There are several limitations: > + > +1) Currently only Debian-based platforms are supported. > + > +2) There is no backwards compatibility with the old IPsec implementation > + that uses Racoon instead of strongSwan for IKE keying. > + > +3) Some older Open vSwitch datapath kernel modules (in Linux Kernel tree) > + do not support route lookups with transport L4 ports properly. In > + this case Ethernet over L4 tunneling protocols (e.g. STT, GENEVE) > + would not work. However, GRE would still work because it does not > + have concept of L4 ports. > + > +4) Some strongSwan versions might not support certain features. For > + example: > + > + a) AES GCM ciphers that improve performance. > + b) xfrm_acq_expires setting in strongSwan configuration file. > + This setting tells strongSwan how aggressively to retry > + establishing tunnel, if peer did not respond to previous keying > + request. > + c) set_proto_port_transport_sa in charon configuration file that tells > + Linux Kernel to filter out unexpected packets that came over IPsec > + tunnel. > + d) IPsec transport mode for NAT deployment. > + > +5) Since IPsec decryption is done from the software when packet has already > + gotten past NIC hardware, then benefits of the most tunneling offloads, > + like GENEVE or TSO for STT can't be leveraged unless NIC has IPsec offloads > + as well and the NIC knows how to leverage them. > + > + > +Setup > +----- > + > +Install strongSwan and openvswitch-ipsec debian packages:: > + > + $ apt-get install strongswan > + $ dpkg -i openvswitch-ipsec_<version>_amd64.deb > + > + > +Configuration > +------------- > + > +IPsec with ovs-monitor-ipsec daemon can be configured in three > +different forwarding policy modes: > + > +1) ovs-monitor-ipsec assumes that packets from all OVS tunnels by default > + should be allowed to exit unencrypted unless particular tunnel is > + explicitly configured with IPsec parameters:: > + > + % ovs-vsctl add-port br0 ipsec_gre0 -- \ > + set interface ipsec_gre0 type=gre \ > + options:remote_ip=1.2.3.4 \ > + options:psk=swordfish]) > + > + > + The problem with this mode is that there is inherent race condition > + between ovs-monitor-ipsec and ovs-vswitchd daemons where ovs-vswitchd > + could enable forwarding before ovs-monitor-ipsec actually had a chance > + to configure IPsec policies. > + This mode should be used only and only if tunnel configuration is static > + and/or if there is firewall that can drop the plain packets that > + occasionally leak the tunnel unencrypted on OVSDB (re)configuration > + events. > + > + > +2) ovs-monitor-ipsec assumes that packets marked with a given SKB mark > + must be encrypted and hence should not be allowed to leave the system > + unencrypted:: > + > + % ovs-vsctl set Open_vSwitch . other_config:default_ipsec_skb_mark=1/1 > + % ovs-vsctl add-port br0 ipsec_gre0 -- \ > + set interface ipsec_gre0 type=gre \ > + options:remote_ip=1.2.3.4 \ > + options:psk=swordfish]) > + > + With this mode ovs-monitor-ipsec makes strongSwan to install IPsec shunt > + policies that serve as safety net and prevent unencrypted tunnel packets > + to leave the host in case ovs-vswitchd configured datapath before > + ovs-monitor-ipsec installed IPsec policies. > + However, assumption here is that OpenFlow controller was careful > + and installed OpenFlow rule with set SKB mark action specified in > + OVSDB Open_vSwitch table before the first packets were able to leave > + the OVS tunnel. > + > +3) ovs-monitor-ipsec assumes that packets coming from all OVS tunnels > + by default need to be protected with IPsec unless the tunnel is explicitly > + allowed to leave unencrypted. This is inverse behavior of the second > + mode:: > + > + % ovs-vsctl set Open_vSwitch . other_config:default_ipsec_skb_mark=0/1 > + % ovs-vsctl add-port br0 ipsec_gre0 -- \ > + set interface gre0 type=gre \ > + options:remote_ip=1.2.3.4 \ > + options:psk=swordfish]) > + > + With this solution ovs-monitor-ipsec tells strongSwan > + to install IPsec shunt policies to match on skb mark 0 > + which should be the default skb mark value for all tunnel packets > + going through NetFilter/XFRM hooks. As a result IPsec assumes > + that all packets coming from tunnels should be encrypted unless > + OpenFlow controller explicitly set skb mark to a non-zero value. > + This is the most secure mode, but at the same time the most intrusive > + one, because the OpenFlow pipeline needs to be overhauled just because > + now IPsec is enabled. > + > + Note that instead of using least significant bit of SKB mark, you could > + just as well have used any other bit. > + > + > +Troubleshooting > +--------------- > + > +Use following ovs-apptcl command to get ovs-monitor-ipsec internal > +representation of tunnel configuration:: > + > + % ovs-appctl -t ovs-monitor-ipsec tunnels/show > + > +If there is misconfiguration then ovs-appctl should indicate why. > +For example:: > + > + Interface name: gre0 v5 (CONFIGURED) <--- Should be set to CONFIGURED. > + Otherwise, error message will > + be provided > + Remote IP: 192.168.2.129 > + Tunnel Type: gre > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: None > + My key: None > + His cert: None > + PSK: swordfish > + Ofport: 1 <--- Whether ovs-vswitchd has assigned Ofport > + number to this Tunnel Port > + CFM state: Up <--- Whether CFM declared this tunnel healthy > + Kernel policies installed: > + ... <--- IPsec policies for this OVS tunnel in > + Linux Kernel installed by strongSwan > + Kernel security associations installed: > + ... <--- IPsec security associations for this OVS > + tunnel in Linux Kernel installed by > + strongswan > + Strongswan connections that are active: > + ... <--- strongSwan "connections" for this OVS > + tunnel > + > + > +Bug Reporting > +------------- > + > +If you think you may have found a bug with security implications, like > + > +1) IPsec protected tunnel accepted packets that came unencrypted; OR > +2) IPsec protected tunnel allowed packets to leave unencrypted; > + > +Then report such bugs according to `security guidelines > +<Documentation/internals/security.rst>`__. > + > +If bug does not have security implications, then report it according to > +instructions in `<REPORTING-bugs.rst>`__. > + > +There is also a possibility that there is a bug in strongSwan. In that > +case report it to strongSwan mailing list. > \ No newline at end of file > diff --git a/Makefile.am b/Makefile.am > index e02799a90..c0fef11fd 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -480,6 +480,7 @@ include tests/automake.mk > include include/automake.mk > include third-party/automake.mk > include debian/automake.mk > +include ipsec/automake.mk > include vswitchd/automake.mk > include ovsdb/automake.mk > include rhel/automake.mk > diff --git a/debian/automake.mk b/debian/automake.mk > index 4d8e204bb..8a8d43c9f 100644 > --- a/debian/automake.mk > +++ b/debian/automake.mk > @@ -20,6 +20,9 @@ EXTRA_DIST += \ > debian/openvswitch-datapath-source.copyright \ > debian/openvswitch-datapath-source.dirs \ > debian/openvswitch-datapath-source.install \ > + debian/openvswitch-ipsec.dirs \ > + debian/openvswitch-ipsec.init \ > + debian/openvswitch-ipsec.install \ > debian/openvswitch-pki.dirs \ > debian/openvswitch-pki.postinst \ > debian/openvswitch-pki.postrm \ > diff --git a/debian/control b/debian/control > index a4c031d85..9443e91c9 100644 > --- a/debian/control > +++ b/debian/control > @@ -320,3 +320,24 @@ Description: Open vSwitch development package > 1000V. > . > This package provides openvswitch headers and libopenvswitch for developers. > + > +Package: openvswitch-ipsec > +Architecture: linux-any > +Depends: iproute2, > + openvswitch-common (= ${binary:Version}), > + openvswitch-switch (= ${binary:Version}), > + python, > + python-openvswitch (= ${source:Version}), > + strongswan, > + ${misc:Depends}, > + ${shlibs:Depends} > +Description: Open vSwitch IPsec tunneling support > + Open vSwitch is a production quality, multilayer, software-based, > + Ethernet virtual switch. It is designed to enable massive network > + automation through programmatic extension, while still supporting > + standard management interfaces and protocols (e.g. NetFlow, IPFIX, > + sFlow, SPAN, RSPAN, CLI, LACP, 802.1ag). In addition, it is designed > + to support distribution across multiple physical servers similar to > + VMware's vNetwork distributed vswitch or Cisco's Nexus 1000V. > + . > + This package provides IPsec tunneling support for OVS tunnels. > diff --git a/debian/openvswitch-ipsec.dirs b/debian/openvswitch-ipsec.dirs > new file mode 100644 > index 000000000..fca44aa7b > --- /dev/null > +++ b/debian/openvswitch-ipsec.dirs > @@ -0,0 +1 @@ > +usr/share/openvswitch/scripts > \ No newline at end of file > diff --git a/debian/openvswitch-ipsec.init b/debian/openvswitch-ipsec.init > new file mode 100644 > index 000000000..1a9683785 > --- /dev/null > +++ b/debian/openvswitch-ipsec.init > @@ -0,0 +1,189 @@ > +#!/bin/sh > +# > +# Copyright (c) 2007, 2009 Javier Fernandez-Sanguino <jfs@debian.org> > +# > +# This is free software; you may redistribute it and/or modify > +# it under the terms of the GNU General Public License as > +# published by the Free Software Foundation; either version 2, > +# or (at your option) any later version. > +# > +# This is distributed in the hope that it will be useful, but > +# WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License with > +# the Debian operating system, in /usr/share/common-licenses/GPL; if > +# not, write to the Free Software Foundation, Inc., 59 Temple Place, > +# Suite 330, Boston, MA 02111-1307 USA > +# > +### BEGIN INIT INFO > +# Provides: openvswitch-ipsec > +# Required-Start: $network $local_fs $remote_fs openvswitch-switch > +# Required-Stop: $remote_fs > +# Default-Start: 2 3 4 5 > +# Default-Stop: 0 1 6 > +# Short-Description: Open vSwitch GRE-over-IPsec daemon > +# Description: The ovs-monitor-ipsec script provides support for encrypting GRE > +# tunnels with IPsec. > +### END INIT INFO > + > +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin > + > +DAEMON=/usr/share/openvswitch/scripts/ovs-monitor-ipsec # Daemon's location > +NAME=ovs-monitor-ipsec # Introduce the short server's name here > +LOGDIR=/var/log/openvswitch # Log directory to use > + > +PIDFILE=/var/run/openvswitch/$NAME.pid > + > +test -x $DAEMON || exit 0 > + > +. /lib/lsb/init-functions > + > +DODTIME=10 # Time to wait for the server to die, in seconds > + # If this value is set too low you might not > + # let some servers to die gracefully and > + # 'restart' will not work > + > +set -e > + > +running_pid() { > +# Check if a given process pid's cmdline matches a given name > + pid=$1 > + name=$2 > + [ -z "$pid" ] && return 1 > + [ ! -d /proc/$pid ] && return 1 > + cmd=`cat /proc/$pid/cmdline | tr "\000" " "|cut -d " " -f 2` > + # Is this the expected server > + [ "$cmd" != "$name" ] && return 1 > + return 0 > +} > + > +running() { > +# Check if the process is running looking at /proc > +# (works for all users) > + > + # No pidfile, probably no daemon present > + [ ! -f "$PIDFILE" ] && return 1 > + pid=`cat $PIDFILE` > + running_pid $pid $DAEMON || return 1 > + return 0 > +} > + > +start_server() { > + if [ ! -d /var/run/openvswitch ]; then > + install -d -m 755 -o root -g root /var/run/openvswitch > + fi > + > + /usr/share/openvswitch/scripts/ovs-monitor-ipsec \ > + --pidfile=$PIDFILE --log-file --detach --monitor \ > + unix:/var/run/openvswitch/db.sock > + > + return 0 > +} > + > +stop_server() { > + if [ -e $PIDFILE ]; then > + kill `cat $PIDFILE` > + fi > + > + return 0 > +} > + > +force_stop() { > +# Force the process to die killing it manually > + [ ! -e "$PIDFILE" ] && return > + if running ; then > + kill -15 $pid > + # Is it really dead? > + sleep "$DODTIME" > + if running ; then > + kill -9 $pid > + sleep "$DODTIME" > + if running ; then > + echo "Cannot kill $NAME (pid=$pid)!" > + exit 1 > + fi > + fi > + fi > + rm -f $PIDFILE > +} > + > + > +case "$1" in > + start) > + log_daemon_msg "Starting $NAME" > + # Check if it's running first > + if running ; then > + log_progress_msg "apparently already running" > + log_end_msg 0 > + exit 0 > + fi > + if start_server && running ; then > + # It's ok, the server started and is running > + log_end_msg 0 > + else > + # Either we could not start it or it is not running > + # after we did > + # NOTE: Some servers might die some time after they start, > + # this code does not try to detect this and might give > + # a false positive (use 'status' for that) > + log_end_msg 1 > + fi > + ;; > + stop) > + log_daemon_msg "Stopping $NAME" > + if running ; then > + # Only stop the server if we see it running > + stop_server > + log_end_msg $? > + else > + # If it's not running don't do anything > + log_progress_msg "apparently not running" > + log_end_msg 0 > + exit 0 > + fi > + ;; > + force-stop) > + # First try to stop gracefully the program > + $0 stop > + if running; then > + # If it's still running try to kill it more forcefully > + log_daemon_msg "Stopping (force) $NAME" > + force_stop > + log_end_msg $? > + fi > + ;; > + restart|force-reload) > + log_daemon_msg "Restarting $NAME" > + stop_server > + # Wait some sensible amount, some server need this > + [ -n "$DODTIME" ] && sleep $DODTIME > + start_server > + running > + log_end_msg $? > + ;; > + status) > + log_daemon_msg "Checking status of $NAME" > + if running ; then > + log_progress_msg "running" > + log_end_msg 0 > + else > + log_progress_msg "apparently not running" > + log_end_msg 1 > + exit 1 > + fi > + ;; > + # Use this if the daemon cannot reload > + reload) > + log_warning_msg "Reloading $NAME daemon: not implemented, as the daemon" > + log_warning_msg "cannot re-read the config file (use restart)." > + ;; > + *) > + N=/etc/init.d/openvswitch-ipsec > + echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2 > + exit 1 > + ;; > +esac > + > +exit 0 > \ No newline at end of file > diff --git a/debian/openvswitch-ipsec.install b/debian/openvswitch-ipsec.install > new file mode 100644 > index 000000000..8fe665cb3 > --- /dev/null > +++ b/debian/openvswitch-ipsec.install > @@ -0,0 +1 @@ > +ipsec/ovs-monitor-ipsec usr/share/openvswitch/scripts > diff --git a/ipsec/automake.mk b/ipsec/automake.mk > new file mode 100644 > index 000000000..12baaed55 > --- /dev/null > +++ b/ipsec/automake.mk > @@ -0,0 +1,9 @@ > +# Copyright (C) 2017 Nicira, Inc. > +# > +# Copying and distribution of this file, with or without modification, > +# are permitted in any medium without royalty provided the copyright > +# notice and this notice are preserved. This file is offered as-is, > +# without warranty of any kind. > + > +EXTRA_DIST += \ > + ipsec/ovs-monitor-ipsec I think it would also make sense to add this utility to the flake8 checks. > diff --git a/ipsec/ovs-monitor-ipsec b/ipsec/ovs-monitor-ipsec > new file mode 100755 > index 000000000..322a7045a > --- /dev/null > +++ b/ipsec/ovs-monitor-ipsec > @@ -0,0 +1,727 @@ > +#!/usr/bin/python Use "/usr/bin/env python" for shebangs for python scripts. This is because some systems (such as NetBSD) will install in other locations (for example, /usr/pkg/bin/python). This also matches with the rest of the python scripts. > +# Copyright (c) 2017 Nicira, Inc. > +# > +# Licensed under the Apache License, Version 2.0 (the "License"); > +# you may not use this file except in compliance with the License. > +# You may obtain a copy of the License at: > +# > +# http://www.apache.org/licenses/LICENSE-2.0 > +# > +# Unless required by applicable law or agreed to in writing, software > +# distributed under the License is distributed on an "AS IS" BASIS, > +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > +# See the License for the specific language governing permissions and > +# limitations under the License. > + > +import argparse > +import glob > +import os > +import re > +import subprocess > +import sys > +from string import Template > + > +from ovs.db import error > +from ovs.db import types > +import ovs.daemon > +import ovs.db.idl > +import ovs.dirs > +import ovs.unixctl > +import ovs.unixctl.server > +import ovs.util > +import ovs.vlog > + > + > +FILE_HEADER = "# Generated by ovs-monitor-ipsec...do not modify by hand!\n\n" > + > +vlog = ovs.vlog.Vlog("ovs-monitor-ipsec") > +exiting = False > +keyer = None > +xfrm = None > + > + > +def unixctl_xfrm_policies(conn, unused_argv, unused_aux): > + global xfrm > + policies = xfrm.get_policies() > + conn.reply(str(policies)) > + > +def unixctl_xfrm_state(conn, unused_argv, unused_aux): > + global xfrm > + securities = xfrm.get_securities() > + conn.reply(str(securities)) > + > +def unixctl_ipsec_status(conn, unused_argv, unused_aux): > + global keyer > + conns = keyer._get_strongswan_conns() > + conn.reply(str(conns)) > + > +def unixctl_show(conn, unused_argv, unused_aux): > + global keyer > + global xfrm > + policies = xfrm.get_policies() > + securities = xfrm.get_securities() > + keyer.show(conn, policies, securities) > + > +def unixctl_exit(conn, unused_argv, unused_aux): > + global exiting > + exiting = True > + conn.reply(None) > + > + > +class XFRM(object): > + """This class is a simple wrapper around ip-xfrm (8) command line > + utility. We are using this class only for informational purposes > + so that ovs-monitor-ipsec could verify that IKE keying daemon has > + installed IPsec policies and security associations into kernel as > + expected.""" > + > + def __init__(self, ip_root_prefix): > + self.IP = ip_root_prefix + "/sbin/ip" > + > + def get_policies(self): > + """This function returns IPsec policies (from kernel) in a dictionary > + where <key> is destination IPv4 address and <value> is SELECTOR of > + the IPsec policy.""" > + policies = {} > + proc = subprocess.Popen([self.IP, 'xfrm', 'policy'], > + stdout=subprocess.PIPE) > + while True: > + line = proc.stdout.readline().strip() > + if line == '': > + break > + a = line.split(" ") > + if len(a) >= 4 and a[0] == "src" and a[2] == "dst": > + dst = (a[3].split("/"))[0] > + if not dst in policies: > + policies[dst] = [] > + policies[dst].append(line) > + src = (a[3].split("/"))[0] > + if not src in policies: > + policies[src] = [] > + policies[src].append(line) > + return policies > + > + def get_securities(self): > + """This function returns IPsec security associations (from kernel) > + in a dictionary where <key> is destination IPv4 address and <value> > + is SELECTOR.""" > + securities = {} > + proc = subprocess.Popen([self.IP, 'xfrm', 'state'], > + stdout=subprocess.PIPE) > + while True: > + line = proc.stdout.readline().strip() > + if line == '': > + break > + a = line.split(" ") > + if len(a) >= 4 and a[0] == "sel" and a[1] == "src" and a[3] == "dst": > + remote_ip = a[4].rstrip().split("/")[0] > + local_ip = a[2].rstrip().split("/")[0] > + if not remote_ip in securities: > + securities[remote_ip] = [] > + securities[remote_ip].append(line) > + if not local_ip in securities: > + securities[local_ip] = [] > + securities[local_ip].append(line) > + return securities > + > + > +class StrongSwanTunnel(object): > + """This class represents IPsec tunnel in strongSwan.""" > + > + transp_tmpl = {"gre" : Template("""\ > +conn $ifname-$version > +$auth_section > + leftsubnet=%dynamic[gre] > + rightsubnet=%dynamic[gre] > + > +"""), "gre64" : Template("""\ > +conn $ifname-$version > +$auth_section > + leftsubnet=%dynamic[gre] > + rightsubnet=%dynamic[gre] > + > +"""), "geneve" : Template("""\ > +conn $ifname-in-$version > +$auth_section > + rightsubnet=%dynamic[udp/%any] > + leftsubnet=%dynamic[udp/6081] > + > +conn $ifname-out-$version > +$auth_section > + rightsubnet=%dynamic[udp/6081] > + leftsubnet=%dynamic[udp/%any] > + > +"""), "stt" : Template("""\ > +conn $ifname-in-$version > +$auth_section > + rightsubnet=%dynamic[tcp/%any] > + leftsubnet=%dynamic[tcp/7471] > + > +conn $ifname-out-$version > +$auth_section > + rightsubnet=%dynamic[tcp/7471] > + leftsubnet=%dynamic[tcp/%any] > + > +"""), "vxlan" : Template("""\ > +conn $ifname-in-$version > +$auth_section > + rightsubnet=%dynamic[udp/%any] > + leftsubnet=%dynamic[udp/4789] > + > +conn $ifname-out-$version > +$auth_section > + rightsubnet=%dynamic[udp/4789] > + leftsubnet=%dynamic[udp/%any] > + > +""")} > + > + auth_tmpl = {"psk" : Template("""\ > + left=$local_ip > + right=$remote_ip > + authby=psk"""), > + "rsa" : Template("""\ > + left=$local_ip > + right=$remote_ip > + rightcert=ovs-$remote_ip.pem > + leftcert=$certificate""")} > + > + unixctl_config_tmpl = Template("""\ > + Remote IP: $remote_ip > + Tunnel Type: $tunnel_type > + Local IP: $local_ip > + SKB mark: $skb_mark > + Use SSL cert: $use_ssl_cert > + My cert: $certificate > + My key: $private_key > + His cert: $peer_cert > + PSK: $psk > +""") > + > + unixctl_status_tmpl = Template("""\ > + Ofport: $ofport > + CFM state: $cfm_state > +""") > + > + def __init__(self, name, row): > + self.name = name # 'name' will not change because it is key in OVSDB > + self.version = 0 # 'version' is increased on configuration changes > + self.last_refreshed_version = -1 > + self.state = "INIT" > + self.conf = {} > + self.status = {} > + self.cert_file = None > + self.update_conf(row) > + > + def update_conf(self, row): > + """This function updates IPsec tunnel configuration by using 'row' > + from OVSDB interface table. If configuration was actually changed > + in OVSDB then this function returns True. Otherwise, it returns > + False.""" > + ret = False > + options = row.options > + use_ssl_cert = options.get("use_ssl_cert") == "true" > + cert = options.get("certificate") > + key = options.get("private_key") > + if use_ssl_cert: # Override with SSL certs if told so > + (cert, key) = (keyer.public_cert, keyer.private_key) > + > + new_conf = { > + "ifname" : self.name, > + "tunnel_type" : row.type, > + "remote_ip" : options.get("remote_ip"), > + "local_ip" : options.get("local_ip", "0.0.0.0"), > + "skb_mark" : keyer.wanted_skb_mark, > + "certificate" : cert, > + "private_key" : key, > + "use_ssl_cert" : use_ssl_cert, > + "peer_cert" : options.get("peer_cert"), > + "psk" : options.get("psk")} > + if self.conf != new_conf: > + # Configuration was updated in OVSDB. Validate it and figure > + # out what to do next with this IPsec tunnel. Also, increment > + # version number of this IPsec tunnel so that we could tell > + # apart old and new tunnels in "ipsec status" output. > + self.version += 1 > + ret = True > + self.conf = new_conf > + if self._validate_conf(): > + self.state = "CONFIGURED" > + else: > + vlog.warn("%s contains invalid configuration%s" % > + (self.name, self.invalid_reason)) > + self.state = "INVALID" > + > + new_status = { > + "cfm_state" : "Up" if row.cfm_fault == [False] else > + "Down" if row.cfm_fault == [True] else > + "Disabled", > + "ofport" : "Not assigned" if (row.ofport in [[], [-1]]) else > + row.ofport[0]} > + if self.status != new_status: > + # Tunnel has become unhealthy or ofport changed. Simply log this. > + vlog.dbg("%s changed status from %s to %s" % > + (self.name, str(self.status), str(new_status))) > + self.status = new_status > + return ret > + > + def mark_for_removal(self): > + """This function marks tunnel for removal. We can't delete tunnel > + right away because we have to clean any files created for this tunnel > + from it's run() function.""" > + self.version += 1 > + self.state = "REMOVED" > + > + def run(self): > + """This function ensures that Tunnel's Finite State Machine moves > + forward.""" > + if self.last_refreshed_version == self.version: > + # Configuration hasn't changed. Exit early. > + return False > + > + if self.state == "CONFIGURED": > + # If configuration changed then cleanup the old state > + # and push the new state > + self._cleanup_old_state() > + self._push_new_state() > + elif self.state == "INVALID": > + # If configuration is invalid then we prevent this > + # tunnel from pushing any configuration to the system > + self._cleanup_old_state() > + elif self.state == "REMOVED": > + self._cleanup_old_state() > + else: > + vlog.fatal("%s is in unexpected state" % self.name) > + > + self.last_refreshed_version = self.version > + return True > + > + def write_config(self, secrets, conf): > + if self.conf["psk"]: > + secrets.write("%s : PSK %s\n" % (self.conf["remote_ip"], > + self.conf["psk"])) > + auth_section = self.auth_tmpl["psk"].substitute(self.conf) > + else: > + secrets.write("%s : RSA %s\n" % (self.conf["remote_ip"], > + self.conf["private_key"])) > + auth_section = self.auth_tmpl["rsa"].substitute(self.conf) > + vals = self.conf.copy() > + vals["auth_section"] = auth_section > + vals["version"] = self.version > + conf.write(self.transp_tmpl[self.conf["tunnel_type"]].substitute(vals)) > + > + def show(self, policies, securities, conns): > + state = self.state > + if self.state == "INVALID": > + state += self.invalid_reason > + header = "Interface name: %s v%u (%s)\n" % (self.name, self.version, > + state) > + conf = self.unixctl_config_tmpl.substitute(self.conf) > + status = self.unixctl_status_tmpl.substitute(self.status) > + spds = "Kernel policies installed:\n" > + remote_ip = self.conf["remote_ip"] > + if remote_ip in policies: > + for line in policies[remote_ip]: > + spds += " " + line + "\n" > + sas = "Kernel security associations installed:\n" > + if remote_ip in securities: > + for line in securities[remote_ip]: > + sas += " " + line + "\n" > + cons = "Strongswan connections that are active:\n" > + if self.name in conns: > + for tname in conns[self.name]: > + cons += " " + conns[self.name][tname] + "\n" > + > + return header + conf + status + spds + sas + cons + "\n" > + > + def _validate_conf(self): > + """This function verifies if IPsec tunnel has valid configuration > + set in 'conf'. If it is valid, then it returns True. Otherwise, > + it returns False and sets the reason why configuration was considered > + as invalid. > + > + This function could be improved in future to also verify validness > + of certificates themselves so that ovs-monitor-ipsec would not > + pass malformed configuration to strongSwan.""" > + > + self.invalid_reason = None > + if not self.conf["remote_ip"]: > + self.invalid_reason = ": 'remote_ip' is not set" > + elif self.conf["peer_cert"]: > + if self.conf["psk"]: > + self.invalid_reason = ": 'psk' must be unset with PKI" > + elif not self.conf["certificate"]: > + self.invalid_reason = ": must set 'certificate' with PKI" > + elif not self.conf["private_key"]: > + self.invalid_reason = ": must set 'private_key' with PKI" > + elif self.conf["psk"]: > + if self.conf["certificate"] or self.conf["private_key"]: > + self.invalid_reason = ": 'certificate', 'private_key' and "\ > + "'use_ssl_cert' must be unset with PSK" > + if self.invalid_reason: > + return False > + return True > + > + def _cleanup_old_state(self): > + if self.cert_file: > + try: > + os.remove(self.cert_file) > + except OSError: > + vlog.warn("could not remove '%s'" % (self.cert_file)) > + self.cert_file = None > + > + def _push_new_state(self): > + if self.conf["peer_cert"]: > + fn = keyer.CERT_DIR + "/ovs-%s.pem" % (self.name) > + try: > + cert = open(fn, "w") > + try: > + cert.write(self.conf["peer_cert"]) > + finally: > + cert.close() > + vlog.info("created peer certificate %s" % (fn)) > + self.cert_file = fn > + except (OSError, IOError) as e: > + vlog.err("could not create certificate '%s'" % (fn)) > + > + > +class StrongSwanKeyer(object): > + """This class is a wrapper around strongSwan.""" > + > + STRONGSWAN_CONF = """%s > +charon.plugins.kernel-netlink.set_proto_port_transport_sa = yes > +charon.plugins.kernel-netlink.xfrm_ack_expires = 10 > +""" % (FILE_HEADER) > + CONF_HEADER = """%s > +config setup > + uniqueids=no > + > +conn %%default > + keyingtries=%%forever > + type=transport > + keyexchange=ikev2 > + auto=route > + ike=aes128gcm12-aesxcbc-modp1024 > + esp=aes128gcm12-modp1024 > + > +""" % (FILE_HEADER) > + SHUNT_POLICY = """conn prevent_unencrypted_gre > + type=drop > + leftprotoport=gre > + mark=%s > + > +conn prevent_unencrypted_stt > + type=drop > + leftprotoport=tcp/7471 > + mark=%s > + > +""" > + > + def __init__(self, strongswan_root_prefix): > + self.private_key = None > + self.public_cert = None > + self.wanted_skb_mark = None > + self.in_use_skb_mark = None > + self.tunnels = {} > + self.CERT_DIR = strongswan_root_prefix + "/etc/ipsec.d/certs" > + self.CHARON_CONF = strongswan_root_prefix + "/etc/strongswan.d/ovs.conf" > + self.IPSEC = strongswan_root_prefix + "/usr/sbin/ipsec" > + self.IPSEC_CONF = strongswan_root_prefix + "/etc/ipsec.conf" > + self.IPSEC_SECRETS = strongswan_root_prefix + "/etc/ipsec.secrets" > + > + def is_tunneling_type_supported(self, tunnel_type): > + """Returns True if we know how to configure IPsec for these > + types of tunnels. Otherwise, returns False. """ > + return tunnel_type in StrongSwanTunnel.transp_tmpl > + > + def is_ipsec_required(self, options_column): > + """Return True if tunnel needs to be encrypted. Otherwise, > + returns False.""" > + return "psk" in options_column or "peer_cert" in options_column > + > + def restart(self): > + """This function restarts strongSwan.""" > + self._initial_configuration() > + vlog.info("restarting strongSwan") > + subprocess.call([self.IPSEC, "restart"]) > + > + def add_tunnel(self, name, row): > + """Adds a new tunnel that keyer will provision with 'name'.""" > + vlog.info("Tunnel %s appeared in OVSDB" % (name)) > + self.tunnels[name] = StrongSwanTunnel(name, row) > + > + def update_tunnel(self, name, row): > + """Updates configuration of already existing tunnel with 'name'.""" > + tunnel = self.tunnels[name] > + if tunnel.update_conf(row): > + vlog.info("Tunnel's '%s' configuration changed in OVSDB to %u" % > + (tunnel.name, tunnel.version)) > + > + def del_tunnel(self, name): > + """Deletes tunnel by 'name'.""" > + vlog.info("Tunnel %s disappeared from OVSDB" % (name)) > + self.tunnels[name].mark_for_removal() > + > + def update_ssl_credentials(self, credentials): > + """Updates already existing """ > + self.public_cert = credentials[0] > + self.private_key = credentials[1] > + > + def update_skb_mark(self, skb_mark): > + """Update skb_mark used in IPsec policies.""" > + self.wanted_skb_mark = skb_mark > + > + def show(self, unix_conn, policies, securities): > + """This function prints all tunnel state in 'unix_conn'. > + It uses 'policies' and securities' received from Linux Kernel > + to show if tunnels were actually configured by strongSwan.""" > + if not self.tunnels: > + unix_conn.reply("No tunnels configured with IPsec") > + return > + s = "" > + conns = self._get_strongswan_conns() > + for name, tunnel in self.tunnels.iteritems(): > + s += tunnel.show(policies, securities, conns) > + unix_conn.reply(s) > + > + def run(self): > + """This function runs state machine that represents whole > + strongSwan configuration (i.e. merged together from individual > + tunnel state machines). It creates configuration files and > + tells strongSwan to update configuration.""" > + needs_refresh = False > + removed_tunnels = [] > + ipsec_secrets = open(self.IPSEC_SECRETS, "w") > + ipsec_conf = open(self.IPSEC_CONF, "w") > + ipsec_secrets.write(FILE_HEADER) > + ipsec_conf.write(self.CONF_HEADER) > + if self.in_use_skb_mark != self.wanted_skb_mark: > + self.in_use_skb_mark = self.wanted_skb_mark > + needs_refresh = True > + > + if self.in_use_skb_mark: > + ipsec_conf.write(" mark_out=%s\n" % self.in_use_skb_mark) > + ipsec_conf.write(self.SHUNT_POLICY % (self.in_use_skb_mark, self.in_use_skb_mark)) > + > + for name, tunnel in self.tunnels.iteritems(): > + if tunnel.run(): > + needs_refresh = True > + > + if tunnel.state == "REMOVED": > + removed_tunnels.append(name) > + elif tunnel.state == "CONFIGURED": > + tunnel.write_config(ipsec_secrets, ipsec_conf) > + ipsec_secrets.close() > + ipsec_conf.close() > + > + for name in removed_tunnels: > + del self.tunnels[name] > + > + if needs_refresh: > + self._refresh() > + > + def _refresh(self): > + """This functions refreshes strongSwan configuration. Behind the > + scenes this function calls: > + 1. once "ipsec update" command that tells strongSwan to load > + all new tunnels from "ipsec.conf"; and > + 2. once "ipsec rereadsecrets" command that tells strongswan to load > + secrets from "ipsec.conf" file > + 3. for every removed tunnel "ipsec stroke down-nb <tunnel>" command > + that removes old tunnels. > + Once strongSwan vici bindings will be distributed with major > + Linux distributions this function could be simplified.""" > + vlog.info("Refreshing strongSwan configuration") > + subprocess.call([self.IPSEC, "update"]) > + subprocess.call([self.IPSEC, "rereadsecrets"]) > + > + # "ipsec update" command does not remove those tunnels that were > + # updated or that disappeared from the ipsec.conf file. So, we have > + # to manually remove them by calling "ipsec stroke down-nb <tunnel>" > + # command. We use <version> number to tell apart tunnels that > + # were just updated. > + # "ipsec down-nb" command is designed to be non-blocking (opposed > + # to "ipsec down" command). This means that we should not be concerned > + # about possibility of ovs-monitor-ipsec to block for each tunnel > + # while strongSwan sends IKE messages over Internet. > + conns_dict = self._get_strongswan_conns() > + for ifname, conns in conns_dict.iteritems(): > + tunnel = self.tunnels.get(ifname) > + for conn in conns: > + # IPsec "connection" names that we choose in strongswan > + # must start with Interface name > + if not conn.startswith(ifname): > + vlog.err("%s does not start with %s" % (conn, ifname)) > + continue > + > + # version number should be the first integer after > + # interface name in IPsec "connection" > + try: > + ver = int(re.findall(r'\d+', conn[len(ifname):])[0]) > + except ValueError, IndexError: > + vlog.err("%s does not contain version number") > + continue > + > + if not tunnel or tunnel.version != ver: > + vlog.info("%s is outdated %u" % (conn, ver)) > + subprocess.call([self.IPSEC, "stroke", "down-nb", conn]) > + > + > + def _get_strongswan_conns(self): > + """This function parses output from 'ipsec status' command. > + It returns dictionary where <key> is interface name (as in OVSDB) > + and <value> is another dictionary. This another dictionary > + uses strongSwan connection name as <key> and more detailed > + sample line from the parsed outpus as <value>. """ > + > + conns = {} > + proc = subprocess.Popen([self.IPSEC, 'status'], > + stdout=subprocess.PIPE) > + while True: > + line = proc.stdout.readline().strip() > + if line == '': > + break > + tunnel_name = line.split(":") > + if len(tunnel_name) < 2: > + continue > + ifname = tunnel_name[0].split("-") > + if len(ifname) < 2: > + continue > + > + if not ifname[0] in conns: > + conns[ifname[0]] = {} > + (conns[ifname[0]])[tunnel_name[0]] = line > + > + return conns > + > + def _initial_configuration(self): > + """This function creates initial configuration that strongSwan should > + receive on startup.""" > + f = open(self.CHARON_CONF, "w") > + f.write(self.STRONGSWAN_CONF) > + f.close() > + > + f = open(self.IPSEC_CONF, "w") > + f.write(self.CONF_HEADER) > + f.close() > + > + f = open(self.IPSEC_SECRETS, "w") > + f.write(FILE_HEADER) > + f.close() > + > + > +def read_ovsdb_open_vswitch_table(data): > + """This functions reads IPsec relevant configuration from Open_vSwitch > + table.""" > + ssl = (None, None) > + global_ipsec_skb_mark = None > + for row in data["Open_vSwitch"].rows.itervalues(): > + if row.ssl: > + ssl = (row.ssl[0].certificate, row.ssl[0].private_key) > + global_ipsec_skb_mark = row.other_config.get("default_ipsec_skb_mark") > + keyer.update_ssl_credentials(ssl) > + keyer.update_skb_mark(global_ipsec_skb_mark) > + > +def read_ovsdb_interface_table(data): > + """This function reads the IPsec relevant configuration from Interface > + table.""" > + ifaces = set() > + for row in data["Interface"].rows.itervalues(): > + if not keyer.is_tunneling_type_supported(row.type): > + continue > + if not keyer.is_ipsec_required(row.options): > + continue > + if row.name in keyer.tunnels: > + keyer.update_tunnel(row.name, row) > + else: > + keyer.add_tunnel(row.name, row) > + ifaces.add(row.name) > + # Mark for removal those tunnels that just disappeared from OVSDB > + for tunnel in keyer.tunnels.keys(): > + if not tunnel in ifaces: > + keyer.del_tunnel(tunnel) > + > +def read_ovsdb(data): > + """This function reads all configuration from OVSDB that ovs-monitor-ipsec > + is interested in.""" > + read_ovsdb_open_vswitch_table(data) > + read_ovsdb_interface_table(data) > + > + > +def main(): > + parser = argparse.ArgumentParser() > + parser.add_argument("database", metavar="DATABASE", > + help="A socket on which ovsdb-server is listening.") > + parser.add_argument("--root-prefix", metavar="DIR", > + help="Use DIR as alternate root directory" > + " (for testing).") > + > + ovs.vlog.add_args(parser) > + ovs.daemon.add_args(parser) > + args = parser.parse_args() > + ovs.vlog.handle_args(args) > + ovs.daemon.handle_args(args) > + > + global keyer > + global xfrm > + > + root_prefix = args.root_prefix if args.root_prefix else "" > + xfrm = XFRM(root_prefix) > + keyer = StrongSwanKeyer(root_prefix) > + > + remote = args.database > + schema_helper = ovs.db.idl.SchemaHelper() > + schema_helper.register_columns("Interface", > + ["name", "type", "options", "cfm_fault", > + "ofport"]) > + schema_helper.register_columns("Open_vSwitch", ["ssl", "other_config"]) > + schema_helper.register_columns("SSL", ["certificate", "private_key"]) > + idl = ovs.db.idl.Idl(remote, schema_helper) > + > + ovs.daemon.daemonize() > + > + ovs.unixctl.command_register("xfrm/policies", "", 0, 0, > + unixctl_xfrm_policies, None) > + ovs.unixctl.command_register("xfrm/state", "", 0, 0, > + unixctl_xfrm_state, None) > + ovs.unixctl.command_register("ipsec/status", "", 0, 0, > + unixctl_ipsec_status, None) > + ovs.unixctl.command_register("tunnels/show", "", 0, 0, > + unixctl_show, None) > + ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None) > + > + error, unixctl_server = ovs.unixctl.server.UnixctlServer.create(None) > + if error: > + ovs.util.ovs_fatal(error, "could not create unixctl server", vlog) > + > + seqno = idl.change_seqno # Sequence number when OVSDB was processed last time > + keyer.restart() > + while True: > + unixctl_server.run() > + if exiting: > + break > + > + idl.run() > + if seqno != idl.change_seqno: > + read_ovsdb(idl.tables) > + seqno = idl.change_seqno > + > + keyer.run() > + > + poller = ovs.poller.Poller() > + unixctl_server.wait(poller) > + idl.wait(poller) > + poller.block() > + > + unixctl_server.close() > + idl.close() > + > + > +if __name__ == '__main__': > + try: > + main() > + except SystemExit: > + # Let system.exit() calls complete normally > + raise > + except: > + vlog.exception("traceback") > + sys.exit(ovs.daemon.RESTART_EXIT_CODE) > diff --git a/tests/automake.mk b/tests/automake.mk > index 8224e5a4a..703b4fa50 100644 > --- a/tests/automake.mk > +++ b/tests/automake.mk > @@ -60,6 +60,7 @@ TESTSUITE_AT = \ > tests/lockfile.at \ > tests/reconnect.at \ > tests/ovs-vswitchd.at \ > + tests/ovs-monitor-ipsec.at \ > tests/dpif-netdev.at \ > tests/dpctl.at \ > tests/ofproto-dpif.at \ > diff --git a/tests/ovs-monitor-ipsec.at b/tests/ovs-monitor-ipsec.at > new file mode 100644 > index 000000000..ec537ad32 > --- /dev/null > +++ b/tests/ovs-monitor-ipsec.at > @@ -0,0 +1,1076 @@ > +# WAIT_FOR_TUNNEL_TO_UPDATE([tunnel], [version], [state]) > +# > +# This macro should be called after ovs-vsctl command that updated > +# IPsec configuration in OVSDB. It basically serves as synchronization > +# barrier to ensures that ovs-monitor-ipsec daemon's internal configuration > +# for 'tunnel' has updated to 'version' and is in 'state'. > +m4_define([WAIT_FOR_TUNNEL_TO_UPDATE], [ > +OVS_WAIT_UNTIL([ovs-appctl -t ovs-monitor-ipsec tunnels/show | grep "Interface name: $1 v$2 ($3"]) > +]) > + > + > +# WAIT_FOR_PROPER_IPSEC_CLEANUP([]) > +# > +# This macro should be called *only* after all IPsec tunnels were removed > +# from OVSDB Interface table. This macro verifies that: > +# 1) ovs-monitor-ipsec daemon thinks that there are no IPsec tunnels > +# 2) ipsec.conf file does not contain any tunnels > +# 3) ipsec.secrets does not contain any 'secrets' for any tunnels > +# 4) ipsec.d directory does not contain any certificates that were > +# previously created by ovs-monitor-ipsec daemon > +m4_define([WAIT_FOR_PROPER_IPSEC_CLEANUP], [ > +OVS_WAIT_UNTIL([ovs-appctl -t ovs-monitor-ipsec tunnels/show | grep "No tunnels configured with IPsec"]) > +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > +]) > +AT_CHECK([cat etc/ipsec.conf], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > + > +config setup > + uniqueids=no > + > +conn %default > + keyingtries=%forever > + type=transport > + keyexchange=ikev2 > + auto=route > + mark_in=1/1 > + ike=aes128gcm12-aesxcbc-modp1024 > + esp=aes128gcm12-modp1024 > + > +]) > +AT_CHECK([ls etc/ipsec.d/certs], [0], [dnl]) > +]) > + > + > +# VERIFY_IPSEC_COMMANDS_EXECUTED([ipsec_commands]) > +# > +# This macro alows to verify that ovs-monitor-ipsec daemon called correct > +# 'ipsec' commands. All ipsec commands are logged by a script that mocks > +# 'ipsec' utility. > +# This macro filters out "ipsec rereadsecrets" and "ipsec status" > +# commands. > +m4_define([VERIFY_IPSEC_COMMANDS_EXECUTED], [ > +AT_CHECK([egrep -v "ipsec status|ipsec rereadsecrets" actions], [0], [$1]) > +]) > + > + > +# INIT_SSL_CERTS([]) > +# > +# This macro creates sample SSL certificates so that it would be > +# possible to test tunnels that use "use_ssl_certs" setting. Don't > +# use these certificates in production. > +m4_define([INIT_SSL_CERTS], [ > +AT_DATA([ssl_key.pem], [ > +-----BEGIN RSA PRIVATE KEY----- > +MIIEpAIBAAKCAQEAxd9ZkUK+sBToEb8x1LSoMfVPw+l4ww915BNn/0TSA7yY+lq/ > +k14+FYHFdqB9vSh5C0bUdhrE7QJ451WP8NyTDIUuZlieUBzonXy0hYVBQ8bUSGfF > +go/4IWb/tHKIlTiZ86GqaBc8uddiZ7cF2beZwv9un9ZHOGtQrvMdzB7FzmzZraJ1 > +NbAsr1GX4w0euBjPbUyu96nzBeKWqX3vdERK1ddL5y8hA2kSUslci60zCf9/jvcV > +aSDvcpLwxKq2QYeS8caPWLQmyszyBrdIvsnrTcUPIG+OOs/w3M+KNYKrySF12hKU > +bUujIrBPt+RJ9ptkv28TYRYqnigRZF3nzNnjewIDAQABAoIBAQCqWh2cZ6APrBAX > +p0lZXKcpS47+lbQ4CsluMB7qr+829FmnwBUK5KoCjhTYild2UK/VO4eSnn1Hp0c7 > +sngX325h6w8FYen6AslpPIGWKiEEHtuH8n8iZpwy9Z/TVH+uKGqyS06QCuFnBb1c > +mT9aLy0bqhktVqc+NXXjCL9wilW13pAMh56zYQ8oMJUvNLLknttv9dYmuIyZ9hg/ > +5al4pN7OqzC9N4AAgDjyHIn3N6t6yfBc/kcJ9OdJ1709DYIr6Nl500Nhf+GZkrEX > +1KTeWPzV6GQD7FUZTJ5eZWNnLbD03823Q0HCElGQkboCUU65EdVdNEHvGdPZw74d > +t2iaXPxxAoGBAPGvd+PByAH98KKeu9z5ysc0AgVOIDTlqhReqMc2jnYTCglWlMcx > +al7K0T6QcSTl3Y2xgGo5O7P+MzZkIiKFPx5fX2d9x6CybEcOQ+5bd6fI0hYF8W18 > +i+i/mmJgGo33UOW6EQfYKAHLn4WdlsKJ9aTaNNCanqkqmS1l+GsrRgYdAoGBANGX > +kl/kfGVYOSRh6ztnRc0i3ONo4RZrDgV0bjEmmujc7V5CIDPxhMUkpsOcG8iCt+55 > +4JqdtLbmfWfeTWWafn8j/Y9DsSv4U0MHc6+Hfit1dmYmZ+ixHjzul9m6H7XsYZ2k > +IpNa7RN+MqvB66pHGpTcC8IQ2fNIkK3GtWLaA3x3AoGBAOX90xtcZxbuLzax05jf > +5MZYiau+wwtTmtyzj+2zzzIxwBVO3VoJfm4il6jwD5vLW2Dhj5CGUnhg6R9TfuBW > +6M/gdounuHcGE+AyhRao2F9EzhfDJBLKuOGOpD4Fsn9y4Psca+SJINlEitO+OZ97 > +ZdWxCR2SZnYZYZdAOHzTu1lJAoGAJcxK9oYzNOerLneGP6lJOkx+P3jLlwppdexg > +bvbCWxp0qFoOiq+UvST1+jLuA8QnPZe3PMsSKyX4GcJKfPdWtsEb2jlf+0kGYwE2 > +CMLLqzS8zIFCngFLLbvtoLNjQqDFnfNa1O5B8RECPF11jbjS/2OLr0zwsWI1zVEX > +pyMgG9MCgYBb+PUIVpo5vRMTsyQ5whRQPK7+5jbU+wFKMnryqfsZ/K6DfeoZzAmn > +KhjeRveOiYEfQZfI5GSfm97TKX5hYKQSwOHa9yzTD25iw4aleU8hc4IbqANzbr/c > +59KynTtKfZXSb/DAd/FTtFvzfwiin5ss4INKbpuBACDrb+mj1nY6bw== > +-----END RSA PRIVATE KEY----- > +]) > +AT_DATA([ssl_cert.pem], [ > +-----BEGIN CERTIFICATE----- > +MIIDgjCCAmoCAQQwDQYJKoZIhvcNAQEFBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD > +VQQIEwJDQTEVMBMGA1UEChMMT3BlbiB2U3dpdGNoMREwDwYDVQQLEwhzd2l0Y2hj > +YTE7MDkGA1UEAxMyT1ZTIHN3aXRjaGNhIENBIENlcnRpZmljYXRlICgyMDE1IEZl > +YiAwOSAwNjoyNjowMykwHhcNMTUwMzEwMTcxMTEzWhcNMjUwMzA3MTcxMTEzWjCB > +izELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRUwEwYDVQQKEwxPcGVuIHZTd2l0 > +Y2gxHzAdBgNVBAsTFk9wZW4gdlN3aXRjaCBjZXJ0aWZpZXIxNzA1BgNVBAMTLmNs > +aWVudCBpZDplMWQxNmQ2MS1jMDcwLTQxOWQtOTYxYi00M2Q4YTBkM2IwMzAwggEi > +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDF31mRQr6wFOgRvzHUtKgx9U/D > +6XjDD3XkE2f/RNIDvJj6Wr+TXj4VgcV2oH29KHkLRtR2GsTtAnjnVY/w3JMMhS5m > +WJ5QHOidfLSFhUFDxtRIZ8WCj/ghZv+0coiVOJnzoapoFzy512JntwXZt5nC/26f > +1kc4a1Cu8x3MHsXObNmtonU1sCyvUZfjDR64GM9tTK73qfMF4papfe90RErV10vn > +LyEDaRJSyVyLrTMJ/3+O9xVpIO9ykvDEqrZBh5Lxxo9YtCbKzPIGt0i+yetNxQ8g > +b446z/Dcz4o1gqvJIXXaEpRtS6MisE+35En2m2S/bxNhFiqeKBFkXefM2eN7AgMB > +AAEwDQYJKoZIhvcNAQEFBQADggEBADmkUbvx9zVtcw1/kSMzr184J3xNBZhmJZ39 > +RBXKR1elZ1aVjc4406vsKZiKKWKQA7rC51xzaLk/huKvUez8RU346GugHtUus05x > +7MZ2DDiwJNb7hJBBiJ9a0ZyhwCBcQJj95g3686TWYGpc8eEPldACf38SP2etPOmE > +QPvIMm3pztNf6JZDFDoV3cwzVEFhQnDHXQE9sR6eLhxpbQqwVJ8tmAr9F5hRr4i7 > +SpxHclvZyS+c3nsNb0xluN1Dh8fcy2ITSXfpar+H5B/i1nZA0dBJsW7ChRHAhPQU > +QYLXEK2+YnilIr00RMv7qa0vF6vC0lXHjGZhm5GzHW2hXDDUIig= > +-----END CERTIFICATE----- > +]) > +AT_CHECK([ovs-vsctl set Open_vSwitch . ssl=@N1 -- --id=@N1 create SSL private_key=ssl_key.pem certificate=ssl_cert.pem bootstrap_ca_cert=true], [0], [ignore]) > +]) > + > + > + > +AT_BANNER([ovs-monitor-ipsec (XFRM)]) > + > +# This test ensures that ovs-monitor-ipsec would properly parse > +# IPsec policies from kernel. > +AT_SETUP([Parse "ip xfrm policy" output]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_MONITOR_IPSEC_START > + > +AT_DATA([sbin/ip], [#!/bin/sh > +echo 'src 192.168.2.128/32 dst 192.168.2.129/32 proto gre > + dir in priority 1794 > + mark 1/0x1 > + tmpl src 0.0.0.0 dst 0.0.0.0 > + proto esp reqid 1 mode transport > +src 192.168.2.129/32 dst 192.168.2.128/32 proto gre > + dir out priority 1794 > + mark 1/0x1 > + tmpl src 0.0.0.0 dst 0.0.0.0 > + proto esp reqid 1 mode transport' > +]) > +chmod +x sbin/ip > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec xfrm/policies], [0], [dnl > +{'192.168.2.129': [['src 192.168.2.128/32 dst 192.168.2.129/32 proto gre', 'src 192.168.2.128/32 dst 192.168.2.129/32 proto gre']], '192.168.2.128': [['src 192.168.2.129/32 dst 192.168.2.128/32 proto gre', 'src 192.168.2.129/32 dst 192.168.2.128/32 proto gre']]} > +]) > + > +OVS_MONITOR_IPSEC_STOP > +AT_CLEANUP > + > + > +# This test ensures that ovs-monitor-ipsec would properly parse > +# IPsec security associations from kernel. > +AT_SETUP([Parse "ip xfrm state" output]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_MONITOR_IPSEC_START > + > +AT_DATA([sbin/ip], [#!/bin/sh > +echo 'src 192.168.2.129 dst 192.168.2.128 > + proto esp spi 0xcdfe5007 reqid 1 mode transport > + replay-window 32 > + mark 1/0x1 > + aead rfc4106(gcm(aes)) 0x3d7cb9ddde30c4e6a870b9c1474b8795a85e564d 96 > + sel src 192.168.2.129/32 dst 192.168.2.128/32 > +src 192.168.2.128 dst 192.168.2.129 > + proto esp spi 0xca623718 reqid 1 mode transport > + replay-window 32 > + mark 1/0x1 > + aead rfc4106(gcm(aes)) 0x7c5b209afe3c93adae68f544f5a1326320b15fe5 96 > + sel src 192.168.2.128/32 dst 192.168.2.129/32' > +]) > +chmod +x sbin/ip > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec xfrm/state], [0], [dnl > +{'192.168.2.129': [['sel src 192.168.2.129/32 dst 192.168.2.128/32', 'sel src 192.168.2.128/32 dst 192.168.2.129/32']], '192.168.2.128': [['sel src 192.168.2.129/32 dst 192.168.2.128/32', 'sel src 192.168.2.128/32 dst 192.168.2.129/32']]} > +]) > +OVS_MONITOR_IPSEC_STOP > +AT_CLEANUP > + > + > +AT_BANNER([ovs-monitor-ipsec (strongSwan)]) > + > +# This test ensures that ovs-monitor-ipsec would ignore non-IPsec tunnels. > +AT_SETUP([Ignore non-IPsec tunnels]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_VSWITCHD_START([]) > +OVS_MONITOR_IPSEC_START > + > +AT_CHECK([ovs-vsctl add-port br0 gre0 -- set interface gre0 type=gre options:remote_ip=1.2.3.4]) > +# Since ovs-monitor-ipsec does not keep track of non-IPsec tunnels then there > +# is no proper way to synchronize this. If ovs-monitor-ipsec did not > +# ignore non-IPsec tunnels then this test would occasionally fail. > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +No tunnels configured with IPsec > +]) > +AT_CHECK([ovs-vsctl del-port br0 gre0]) > + > +WAIT_FOR_PROPER_IPSEC_CLEANUP > +OVS_MONITOR_IPSEC_STOP > +OVS_VSWITCHD_STOP > +AT_CLEANUP > + > + > +# This test ensures that ovs-monitor-ipsec would properly parse > +# output from "ipsec status" command > +AT_SETUP([Parse "ipsec status" output]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_MONITOR_IPSEC_START > + > +AT_DATA([active_tunns], [Routed Connections: > + stt0-out-2{3}: ROUTED, TRANSPORT > + stt0-out-2{3}: 192.168.2.128/32[[tcp]] === 192.168.2.129/32[[tcp/7471]] > + stt0-in-2{2}: ROUTED, TRANSPORT > + stt0-in-2{2}: 192.168.2.128/32[[tcp/7471]] === 192.168.2.129/32[[tcp]] > +Security Associations (1 up, 0 connecting): > + stt0-in-2[[3]]: ESTABLISHED 3 minutes ago, 192.168.2.128[[192.168.2.128]]...192.168.2.129[[192.168.2.129]] > + stt0-in-2{2}: INSTALLED, TRANSPORT, ESP SPIs: cbc3e903_i c29b1e5c_o > + stt0-in-2{2}: 192.168.2.128/32[[tcp/7471]] === 192.168.2.129/32[[tcp]] > + stt0-out-2{3}: INSTALLED, TRANSPORT, ESP SPIs: c958d9fb_i c45ce6b0_o > + stt0-out-2{3}: 192.168.2.128/32[[tcp]] === 192.168.2.129/32[[tcp/7471]] > +]) > + > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec ipsec/status], [0], [dnl > +{'stt0': {'stt0-in-2[[3]]': 'stt0-in-2[[3]]: ESTABLISHED 3 minutes ago, 192.168.2.128[[192.168.2.128]]...192.168.2.129[[192.168.2.129]]:', 'stt0-in-2{2}': 'stt0-in-2{2}: 192.168.2.128/32[[tcp/7471]] === 192.168.2.129/32[[tcp]]:', 'stt0-out-2{3}': 'stt0-out-2{3}: 192.168.2.128/32[[tcp]] === 192.168.2.129/32[[tcp/7471]]:'}} > +]) > + > +WAIT_FOR_PROPER_IPSEC_CLEANUP > +OVS_MONITOR_IPSEC_STOP > +AT_CLEANUP > + > + > +# This test ensures that ovs-monitor-ipsec would properly configure > +# ipsec_gre tunnel with Pre-Shared Key > +AT_SETUP([ipsec_gre with PSK authentication]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_VSWITCHD_START([]) > +OVS_MONITOR_IPSEC_START > + > +AT_CHECK([ovs-vsctl add-port br0 gre0 -- \ > + set interface gre0 type=ipsec_gre \ > + options:remote_ip=1.2.3.4 \ > + options:psk=swordfish]) > +WAIT_FOR_TUNNEL_TO_UPDATE([gre0], [1], [CONFIGURED]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: gre0 v1 (CONFIGURED) > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_gre > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: None > + My key: None > + His cert: None > + PSK: swordfish > + Ofport: 1 > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + gre0-1: > + > +]) > +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > +1.2.3.4 : PSK swordfish > +]) > +AT_CHECK([cat etc/ipsec.conf], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > + > +config setup > + uniqueids=no > + > +conn %default > + keyingtries=%forever > + type=transport > + keyexchange=ikev2 > + auto=route > + mark_in=1/1 > + ike=aes128gcm12-aesxcbc-modp1024 > + esp=aes128gcm12-modp1024 > + > +conn gre0-1 > + left=0.0.0.0 > + right=1.2.3.4 > + authby=psk > + leftsubnet=%dynamic[[gre]] > + rightsubnet=%dynamic[[gre]] > + > +]) > +AT_CHECK([ovs-vsctl del-port gre0]) > + > +WAIT_FOR_PROPER_IPSEC_CLEANUP > +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl > +ipsec restart > +ipsec update > +ipsec update > +ipsec stroke down-nb gre0-1 > +]) > +OVS_MONITOR_IPSEC_STOP > +OVS_VSWITCHD_STOP > +AT_CLEANUP > + > + > +# This test ensures that ovs-monitor-ipsec would properly configure > +# ipsec_stt tunnel with Pre-Shared Key > +AT_SETUP([ipsec_stt with PSK authentication]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_VSWITCHD_START([]) > +OVS_MONITOR_IPSEC_START > + > +AT_CHECK([ovs-vsctl add-port br0 stt0 -- \ > + set interface stt0 type=ipsec_stt \ > + options:remote_ip=1.2.3.4 \ > + options:psk=swordfish]) > +WAIT_FOR_TUNNEL_TO_UPDATE([stt0], [1], [CONFIGURED]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: stt0 v1 (CONFIGURED) > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_stt > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: None > + My key: None > + His cert: None > + PSK: swordfish > + Ofport: 1 > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + stt0-in-1: > + stt0-out-1: > + > +]) > +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > +1.2.3.4 : PSK swordfish > +]) > +AT_CHECK([cat etc/ipsec.conf], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > + > +config setup > + uniqueids=no > + > +conn %default > + keyingtries=%forever > + type=transport > + keyexchange=ikev2 > + auto=route > + mark_in=1/1 > + ike=aes128gcm12-aesxcbc-modp1024 > + esp=aes128gcm12-modp1024 > + > +conn stt0-in-1 > + left=0.0.0.0 > + right=1.2.3.4 > + authby=psk > + rightsubnet=%dynamic[[tcp/%any]] > + leftsubnet=%dynamic[[tcp/7471]] > + > +conn stt0-out-1 > + left=0.0.0.0 > + right=1.2.3.4 > + authby=psk > + rightsubnet=%dynamic[[tcp/7471]] > + leftsubnet=%dynamic[[tcp/%any]] > + > +]) > +AT_CHECK([ovs-vsctl del-port stt0]) > + > +WAIT_FOR_PROPER_IPSEC_CLEANUP > +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl > +ipsec restart > +ipsec update > +ipsec update > +ipsec stroke down-nb stt0-in-1 > +ipsec stroke down-nb stt0-out-1 > +]) > +OVS_MONITOR_IPSEC_STOP > +OVS_VSWITCHD_STOP > +AT_CLEANUP > + > + > +# This test ensures that ovs-monitor-ipsec would properly configure > +# ipsec_geneve tunnel with Pre-Shared Key > +AT_SETUP([ipsec_geneve with PSK authentication]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_VSWITCHD_START([]) > +OVS_MONITOR_IPSEC_START > + > +AT_CHECK([ovs-vsctl add-port br0 geneve0 -- \ > + set interface geneve0 type=ipsec_geneve \ > + options:remote_ip=1.2.3.4 \ > + options:psk=swordfish]) > +WAIT_FOR_TUNNEL_TO_UPDATE([geneve0], [1], [CONFIGURED]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: geneve0 v1 (CONFIGURED) > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_geneve > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: None > + My key: None > + His cert: None > + PSK: swordfish > + Ofport: 1 > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + geneve0-in-1: > + geneve0-out-1: > + > +]) > +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > +1.2.3.4 : PSK swordfish > +]) > +AT_CHECK([cat etc/ipsec.conf], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > + > +config setup > + uniqueids=no > + > +conn %default > + keyingtries=%forever > + type=transport > + keyexchange=ikev2 > + auto=route > + mark_in=1/1 > + ike=aes128gcm12-aesxcbc-modp1024 > + esp=aes128gcm12-modp1024 > + > +conn geneve0-in-1 > + left=0.0.0.0 > + right=1.2.3.4 > + authby=psk > + rightsubnet=%dynamic[[udp/%any]] > + leftsubnet=%dynamic[[udp/6081]] > + > +conn geneve0-out-1 > + left=0.0.0.0 > + right=1.2.3.4 > + authby=psk > + rightsubnet=%dynamic[[udp/6081]] > + leftsubnet=%dynamic[[udp/%any]] > + > +]) > +AT_CHECK([ovs-vsctl del-port geneve0]) > + > +WAIT_FOR_PROPER_IPSEC_CLEANUP > +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl > +ipsec restart > +ipsec update > +ipsec update > +ipsec stroke down-nb geneve0-in-1 > +ipsec stroke down-nb geneve0-out-1 > +]) > +OVS_MONITOR_IPSEC_STOP > +OVS_VSWITCHD_STOP > +AT_CLEANUP > + > + > +# This test ensures that ovs-monitor-ipsec would properly configure > +# ipsec_vxlan tunnel with Pre-Shared Key > +AT_SETUP([ipsec_vxlan with PSK authentication]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_VSWITCHD_START([]) > +OVS_MONITOR_IPSEC_START > + > +AT_CHECK([ovs-vsctl add-port br0 vxlan0 -- \ > + set interface vxlan0 type=ipsec_vxlan \ > + options:remote_ip=1.2.3.4 \ > + options:psk=swordfish]) > +WAIT_FOR_TUNNEL_TO_UPDATE([vxlan0], [1], [CONFIGURED]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: vxlan0 v1 (CONFIGURED) > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_vxlan > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: None > + My key: None > + His cert: None > + PSK: swordfish > + Ofport: 1 > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + vxlan0-in-1: > + vxlan0-out-1: > + > +]) > +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > +1.2.3.4 : PSK swordfish > +]) > +AT_CHECK([cat etc/ipsec.conf], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > + > +config setup > + uniqueids=no > + > +conn %default > + keyingtries=%forever > + type=transport > + keyexchange=ikev2 > + auto=route > + mark_in=1/1 > + ike=aes128gcm12-aesxcbc-modp1024 > + esp=aes128gcm12-modp1024 > + > +conn vxlan0-in-1 > + left=0.0.0.0 > + right=1.2.3.4 > + authby=psk > + rightsubnet=%dynamic[[udp/%any]] > + leftsubnet=%dynamic[[udp/4789]] > + > +conn vxlan0-out-1 > + left=0.0.0.0 > + right=1.2.3.4 > + authby=psk > + rightsubnet=%dynamic[[udp/4789]] > + leftsubnet=%dynamic[[udp/%any]] > + > +]) > +AT_CHECK([ovs-vsctl del-port vxlan0]) > + > +WAIT_FOR_PROPER_IPSEC_CLEANUP > +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl > +ipsec restart > +ipsec update > +ipsec update > +ipsec stroke down-nb vxlan0-in-1 > +ipsec stroke down-nb vxlan0-out-1 > +]) > +OVS_MONITOR_IPSEC_STOP > +OVS_VSWITCHD_STOP > +AT_CLEANUP > + > + > +# This test ensures that ovs-monitor-ipsec is able to configure IPsec for > +# a tunnel that wants to reuse Open vSwitch SSL certificates for > +# authentication. Note that previous PSK tests already verified geneve, > +# vxlan and stt IPsec tunneling. So there is not much benefit of redoing > +# these kind of tests for those tunneling protocols. > +AT_SETUP([ipsec_gre with PKI authentication (use_ssl_cert)]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_VSWITCHD_START([]) > +OVS_MONITOR_IPSEC_START > +INIT_SSL_CERTS > + > +AT_CHECK([ovs-vsctl add-port br0 gre0 -- \ > + set interface gre0 type=ipsec_gre \ > + options:remote_ip=1.2.3.4 \ > + options:use_ssl_cert=true \ > + options:peer_cert="asd"]) > +WAIT_FOR_TUNNEL_TO_UPDATE([gre0], [1], [CONFIGURED]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: gre0 v1 (CONFIGURED) > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_gre > + Local IP: 0.0.0.0 > + Use SSL cert: True > + My cert: ssl_cert.pem > + My key: ssl_key.pem > + His cert: asd > + PSK: None > + Ofport: 1 > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + gre0-1: > + > +]) > +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > +1.2.3.4 : RSA ssl_key.pem > +]) > +AT_CHECK([cat etc/ipsec.d/certs/ovs-gre0.pem], [0], [dnl > +asd]) > +AT_CHECK([cat etc/ipsec.conf], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > + > +config setup > + uniqueids=no > + > +conn %default > + keyingtries=%forever > + type=transport > + keyexchange=ikev2 > + auto=route > + mark_in=1/1 > + ike=aes128gcm12-aesxcbc-modp1024 > + esp=aes128gcm12-modp1024 > + > +conn gre0-1 > + left=0.0.0.0 > + right=1.2.3.4 > + rightcert=ovs-1.2.3.4.pem > + leftcert=ssl_cert.pem > + leftsubnet=%dynamic[[gre]] > + rightsubnet=%dynamic[[gre]] > + > +]) > +AT_CHECK([ovs-vsctl del-port gre0]) > + > +WAIT_FOR_PROPER_IPSEC_CLEANUP > +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl > +ipsec restart > +ipsec update > +ipsec update > +ipsec stroke down-nb gre0-1 > +]) > +OVS_MONITOR_IPSEC_STOP > +OVS_VSWITCHD_STOP > +AT_CLEANUP > + > + > +# This test ensures that ovs-monitor-ipsec would be able to configure > +# IPsec tunnel that has it's own Private and Public key (opposed to > +# reusing Open vSwitch's SSL credentials) > +AT_SETUP([ipsec_gre with PKI authentication]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_VSWITCHD_START([]) > +OVS_MONITOR_IPSEC_START > + > +AT_CHECK([ovs-vsctl add-port br0 gre0 -- \ > + set interface gre0 type=ipsec_gre \ > + options:remote_ip=1.2.3.4 \ > + options:private_key="private_key.pem" \ > + options:certificate="public_key.pem" \ > + options:peer_cert="asd"]) > +WAIT_FOR_TUNNEL_TO_UPDATE([gre0], [1], [CONFIGURED]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: gre0 v1 (CONFIGURED) > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_gre > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: public_key.pem > + My key: private_key.pem > + His cert: asd > + PSK: None > + Ofport: 1 > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + gre0-1: > + > +]) > +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > +1.2.3.4 : RSA private_key.pem > +]) > +AT_CHECK([cat etc/ipsec.conf], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > + > +config setup > + uniqueids=no > + > +conn %default > + keyingtries=%forever > + type=transport > + keyexchange=ikev2 > + auto=route > + mark_in=1/1 > + ike=aes128gcm12-aesxcbc-modp1024 > + esp=aes128gcm12-modp1024 > + > +conn gre0-1 > + left=0.0.0.0 > + right=1.2.3.4 > + rightcert=ovs-1.2.3.4.pem > + leftcert=public_key.pem > + leftsubnet=%dynamic[[gre]] > + rightsubnet=%dynamic[[gre]] > + > +]) > +AT_CHECK([ovs-vsctl del-port gre0]) > + > +WAIT_FOR_PROPER_IPSEC_CLEANUP > +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl > +ipsec restart > +ipsec update > +ipsec update > +ipsec stroke down-nb gre0-1 > +]) > +OVS_MONITOR_IPSEC_STOP > +OVS_VSWITCHD_STOP > +AT_CLEANUP > + > + > +# This test ensures that ovs-monitor-ipsec is able to configure > +# two IPsec tunnels without interfering with each other. > +AT_SETUP([ipsec_gre and ipsec_stt to two different hosts]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_VSWITCHD_START([]) > +OVS_MONITOR_IPSEC_START > + > + > +AT_CHECK([ovs-vsctl add-port br0 gre0 -- \ > + set interface gre0 type=ipsec_gre \ > + options:remote_ip=1.2.3.4 \ > + options:private_key="private_key1.pem" \ > + options:certificate="public_key1.pem" \ > + options:peer_cert="asd"]) > +AT_CHECK([ovs-vsctl add-port br0 stt0 -- \ > + set interface stt0 type=ipsec_stt \ > + options:remote_ip=1.2.3.5 \ > + options:private_key="private_key2.pem" \ > + options:certificate="public_key2.pem" \ > + options:peer_cert="fgh"]) > +WAIT_FOR_TUNNEL_TO_UPDATE([gre0], [1], [CONFIGURED]) > +WAIT_FOR_TUNNEL_TO_UPDATE([stt0], [1], [CONFIGURED]) > +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > +1.2.3.4 : RSA private_key1.pem > +1.2.3.5 : RSA private_key2.pem > +]) > +AT_CHECK([cat etc/ipsec.conf], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > + > +config setup > + uniqueids=no > + > +conn %default > + keyingtries=%forever > + type=transport > + keyexchange=ikev2 > + auto=route > + mark_in=1/1 > + ike=aes128gcm12-aesxcbc-modp1024 > + esp=aes128gcm12-modp1024 > + > +conn gre0-1 > + left=0.0.0.0 > + right=1.2.3.4 > + rightcert=ovs-1.2.3.4.pem > + leftcert=public_key1.pem > + leftsubnet=%dynamic[[gre]] > + rightsubnet=%dynamic[[gre]] > + > +conn stt0-in-1 > + left=0.0.0.0 > + right=1.2.3.5 > + rightcert=ovs-1.2.3.5.pem > + leftcert=public_key2.pem > + rightsubnet=%dynamic[[tcp/%any]] > + leftsubnet=%dynamic[[tcp/7471]] > + > +conn stt0-out-1 > + left=0.0.0.0 > + right=1.2.3.5 > + rightcert=ovs-1.2.3.5.pem > + leftcert=public_key2.pem > + rightsubnet=%dynamic[[tcp/7471]] > + leftsubnet=%dynamic[[tcp/%any]] > + > +]) > +AT_CHECK([ovs-vsctl del-port gre0]) > +AT_CHECK([ovs-vsctl del-port stt0]) > + > +WAIT_FOR_PROPER_IPSEC_CLEANUP > +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl > +ipsec restart > +ipsec update > +ipsec update > +ipsec update > +ipsec stroke down-nb gre0-1 > +ipsec update > +ipsec stroke down-nb stt0-in-1 > +ipsec stroke down-nb stt0-out-1 > +]) > +OVS_MONITOR_IPSEC_STOP > +OVS_VSWITCHD_STOP > +AT_CLEANUP > + > + > +# This test ensures that ovs-monitor-ipsec is able to configure two > +# IPsec tunnels simulatenously to the same remote_ip. > +AT_SETUP([ipsec_gre and ipsec_stt to the same host]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_VSWITCHD_START([]) > +OVS_MONITOR_IPSEC_START > + > + > +AT_CHECK([ovs-vsctl add-port br0 gre0 -- \ > + set interface gre0 type=ipsec_gre \ > + options:remote_ip=1.2.3.4 \ > + options:private_key="private_key1.pem" \ > + options:certificate="public_key1.pem" \ > + options:peer_cert="asd"]) > +AT_CHECK([ovs-vsctl add-port br0 stt0 -- \ > + set interface stt0 type=ipsec_stt \ > + options:remote_ip=1.2.3.4 \ > + options:private_key="private_key2.pem" \ > + options:certificate="public_key2.pem" \ > + options:peer_cert="fgh"]) > +WAIT_FOR_TUNNEL_TO_UPDATE([stt0], [1], [CONFIGURED]) > +WAIT_FOR_TUNNEL_TO_UPDATE([stt0], [1], [CONFIGURED]) > +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > +1.2.3.4 : RSA private_key1.pem > +1.2.3.4 : RSA private_key2.pem > +]) > +AT_CHECK([cat etc/ipsec.conf], [0], [dnl > +# Generated by ovs-monitor-ipsec...do not modify by hand! > + > + > +config setup > + uniqueids=no > + > +conn %default > + keyingtries=%forever > + type=transport > + keyexchange=ikev2 > + auto=route > + mark_in=1/1 > + ike=aes128gcm12-aesxcbc-modp1024 > + esp=aes128gcm12-modp1024 > + > +conn gre0-1 > + left=0.0.0.0 > + right=1.2.3.4 > + rightcert=ovs-1.2.3.4.pem > + leftcert=public_key1.pem > + leftsubnet=%dynamic[[gre]] > + rightsubnet=%dynamic[[gre]] > + > +conn stt0-in-1 > + left=0.0.0.0 > + right=1.2.3.4 > + rightcert=ovs-1.2.3.4.pem > + leftcert=public_key2.pem > + rightsubnet=%dynamic[[tcp/%any]] > + leftsubnet=%dynamic[[tcp/7471]] > + > +conn stt0-out-1 > + left=0.0.0.0 > + right=1.2.3.4 > + rightcert=ovs-1.2.3.4.pem > + leftcert=public_key2.pem > + rightsubnet=%dynamic[[tcp/7471]] > + leftsubnet=%dynamic[[tcp/%any]] > + > +]) > +AT_CHECK([ovs-vsctl del-port gre0]) > +AT_CHECK([ovs-vsctl del-port stt0]) > + > +WAIT_FOR_PROPER_IPSEC_CLEANUP > +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl > +ipsec restart > +ipsec update > +ipsec update > +ipsec update > +ipsec stroke down-nb gre0-1 > +ipsec update > +ipsec stroke down-nb stt0-in-1 > +ipsec stroke down-nb stt0-out-1 > +]) > +OVS_MONITOR_IPSEC_STOP > +OVS_VSWITCHD_STOP > +AT_CLEANUP > + > + > +# This test ensures that ovs-monitor-ipsec can properly update IPsec tunnel's > +# configuration by calling "ipsec update" and "ipsec stroke down-nb" > +# commands whenver OVSDB configuration changes. > +AT_SETUP([Sequence of IPsec tunnel reconfiguration events]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +AT_SKIP_IF([$non_ascii_cwd]) > +OVS_VSWITCHD_START([]) > +OVS_MONITOR_IPSEC_START > + > +# Start with tunnel that has 'type' set to 'ipsec_stt' and nothing else > +ovs-vsctl add-port br0 tun0 -- set interface tun0 type=ipsec_stt > +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [1], [INVALID]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: tun0 v1 (INVALID: 'remote_ip' is not set) > + Remote IP: None > + Tunnel Type: ipsec_stt > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: None > + My key: None > + His cert: None > + PSK: None > + Ofport: Not assigned > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + > +]) > + > +ovs-vsctl set interface tun0 options:remote_ip="1.2.3.4" > +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [2], [INVALID]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: tun0 v2 (INVALID: must set either 'psk' or 'peer_cert') > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_stt > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: None > + My key: None > + His cert: None > + PSK: None > + Ofport: Not assigned > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + > +]) > + > +ovs-vsctl set interface tun0 options:psk=swordfish > +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [3], [CONFIGURED]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: tun0 v3 (CONFIGURED) > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_stt > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: None > + My key: None > + His cert: None > + PSK: swordfish > + Ofport: 1 > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + tun0-out-3: > + tun0-in-3: > + > +]) > + > + > +ovs-vsctl set interface tun0 type=ipsec_gre > +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [4], [CONFIGURED]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: tun0 v4 (CONFIGURED) > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_gre > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: None > + My key: None > + His cert: None > + PSK: swordfish > + Ofport: 1 > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + tun0-4: > + > +]) > + > + > +ovs-vsctl remove interface tun0 options psk --\ > + set Interface tun0 options:peer_cert="asd" > +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [5], [INVALID]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: tun0 v5 (INVALID: must set 'certificate' with PKI) > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_gre > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: None > + My key: None > + His cert: asd > + PSK: None > + Ofport: Not assigned > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + > +]) > + > + > +ovs-vsctl set Interface tun0 options:certificate="my_cert.pem" > +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [6], [INVALID]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: tun0 v6 (INVALID: must set 'private_key' with PKI) > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_gre > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: my_cert.pem > + My key: None > + His cert: asd > + PSK: None > + Ofport: 2 > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + > +]) > + > + > +ovs-vsctl set Interface tun0 options:private_key="my_key.pem" > +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [7], [CONFIGURED]) > +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl > +Interface name: tun0 v7 (CONFIGURED) > + Remote IP: 1.2.3.4 > + Tunnel Type: ipsec_gre > + Local IP: 0.0.0.0 > + Use SSL cert: False > + My cert: my_cert.pem > + My key: my_key.pem > + His cert: asd > + PSK: None > + Ofport: 2 > + CFM state: Disabled > +Kernel policies installed: > +Kernel security associations installed: > +Strongswan connections that are active: > + tun0-7: > + > +]) > + > +AT_CHECK([ovs-vsctl del-port tun0]) > +WAIT_FOR_PROPER_IPSEC_CLEANUP > +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl > +ipsec restart > +ipsec update > +ipsec update > +ipsec update > +ipsec update > +ipsec stroke down-nb tun0-in-3 > +ipsec stroke down-nb tun0-out-3 > +ipsec update > +ipsec stroke down-nb tun0-4 > +ipsec update > +ipsec update > +ipsec update > +ipsec stroke down-nb tun0-7 > +]) > +# For this test all errors in OVS logs that contain 'netdev' must be ignored > +# because ovs-vswitchd also does basic validation > +OVS_MONITOR_IPSEC_STOP > +OVS_VSWITCHD_STOP(["/netdev/d"]) > +AT_CLEANUP > diff --git a/tests/testsuite.at b/tests/testsuite.at > index 15c385e2c..23b7defbd 100644 > --- a/tests/testsuite.at > +++ b/tests/testsuite.at > @@ -55,6 +55,7 @@ m4_include([tests/ovs-router.at]) > m4_include([tests/lockfile.at]) > m4_include([tests/reconnect.at]) > m4_include([tests/ovs-vswitchd.at]) > +m4_include([tests/ovs-monitor-ipsec.at]) > m4_include([tests/ofproto.at]) > m4_include([tests/dpif-netdev.at]) > m4_include([tests/pmd.at]) > diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml > index b93da69bd..205578c87 100644 > --- a/vswitchd/vswitch.xml > +++ b/vswitchd/vswitch.xml > @@ -473,8 +473,15 @@ > OpenFlow specification mandates the timeout to be at least one > second. The default is 10 seconds. > </p> > - </column> > - > + </column> > + <column name="other_config" key="ipsec_default_mark" > + type='{"type": "string"}'> > + <p> > + Defines the value and mask of SKB mark to use in IPsec shunt > + policies. There are three packet forwarding modes in which > + IPsec could operate. Would be good to list the format of this option. "Value and mask" usually implies parts. "There are three packet forwarding modes" - does it make sense to describe the selection of those modes here? > + </p> > + </column> > </group> > > <group title="Status"> > @@ -1379,7 +1386,7 @@ > <column name="external_ids"/> > </group> > </table> > - > + > <table name="Port" table="Port or bond configuration."> > <p>A port within a <ref table="Bridge"/>.</p> > <p>Most commonly, a port has exactly one ``interface,'' pointed to by its > @@ -2625,7 +2632,7 @@ > > <column name="options" key="csum" type='{"type": "boolean"}'> > <p> > - Optional. Compute encapsulation header (either GRE or UDP) > + Optional. Compute encapsulation header (either GRE or UDP) > checksums on outgoing packets. Default is disabled, set to > <code>true</code> to enable. Checksums present on incoming > packets will be validated regardless of this setting. > @@ -2643,6 +2650,74 @@ > > </column> > </group> > + > + <group title="Tunnel Options: IPsec"> > + <p> > + <code>gre</code>, <code>geneve</code>, and > + <code>vxlan</code> interfaces support these options. > + </p> > + > + <column name="options" key="psk" type='{"type": "boolean"}'> > + <p> > + The preshared secret to negotiate tunnel in PSK mode. > + Must be unset when peer_cert is set. > + </p> > + > + <p> > + This secret gets populated by ovs-monitor-ipsec daemon > + in /etc/ipsec.secrets file. This value must match on both > + tunnel ends, because otherwise authentication failure will be > + triggered in /var/log/auth.log file by strongSwan. > + </p> > + > + </column> > + > + <column name="options" key="peer_cert" type='{"type": "boolean"}'> > + <p> > + The public key of remote peer to negotiate tunnel in PKI mode. > + Must be unset when psk is set. > + </p> > + > + <p> > + The ovs-monitor-ipsec daemon creates a file with content of this > + value under /etc/ipsec.d/certs directory and then references it > + from /etc/ipsec.secrets file. This value must be set either to > + peer's public certificate contents itself or to the Certificate > + Authority of that peer. > + </p> > + > + </column> > + > + <column name="options" key="private_key" type='{"type": "boolean"}'> > + <p> > + The private key of this peer to negotiate tunnel in PKI mode. > + This file must never ever be leaked, because anyone who posses > + it can negotiate tunnel. This is awkwardly phrased. > + Must be unset when psk is set. > + </p> > + > + <p> > + ... > + </p> > + > + </column> > + > + <column name="options" key="certificate" type='{"type": "boolean"}'> > + <p> > + The public key of this peer to negotiate tunnel in PKI mode. > + This file is exhanged over wire during IKE negotiation phase. s/exhanged/exchanged/ > + Hence it is not a secret. > + Must be unset when psk is set. > + </p> > + > + <p> > + ... > + </p> > + > + </column> > + > + </group> > + > </group> > > <group title="Tunnel Options: erspan only">
On Wed, Jun 27, 2018 at 04:00:00PM -0400, Aaron Conole wrote: > Thanks for the patch. It's really cool to see IPSec being > re-integrated. Just a few quick comments inline. > > Qiuyu Xiao <qiuyu.xiao.qyx@gmail.com> writes: > > > From: Ansis Atteka <aatteka@ovn.org> > > > > This patch reintroduces ovs-monitor-ipsec daemon that > > was previously removed by commit 2b02d770 ("openvswitch: > > Allow external IPsec tunnel management.") > > > > The reason for removal at the time was that there were IPsec > > flavoured tunnel types, like ipsec_gre, that consumed for > > themselves the least significant bit of SKB mark irrelevant > > whether Open vSwitch users were using IPsec or not. > > > > After this patch, there are no IPsec flavored tunnels anymore. > > IPsec is enabled by setting up the right values in: > > 1. OVSDB:Interface:options column; > > 2. OVSDB:Open_vSwitch:other_config column; > > 3. OpenFlow pipeline. > > > > Signed-off-by: Ansis Atteka <aatteka@ovn.org> > > --- > > Have you looked into what it would take to support other systems (for > instance, BSDs, Fedora/RHEL, CentOS)? I've asked Qiuyu to look at RHEL/Fedora support soon. It will take extra work because Red Hat flavored systems use libreswan instead of strongswan, and libreswan configuration works differently.
Ben Pfaff <blp@ovn.org> writes: > On Wed, Jun 27, 2018 at 04:00:00PM -0400, Aaron Conole wrote: >> Thanks for the patch. It's really cool to see IPSec being >> re-integrated. Just a few quick comments inline. >> >> Qiuyu Xiao <qiuyu.xiao.qyx@gmail.com> writes: >> >> > From: Ansis Atteka <aatteka@ovn.org> >> > >> > This patch reintroduces ovs-monitor-ipsec daemon that >> > was previously removed by commit 2b02d770 ("openvswitch: >> > Allow external IPsec tunnel management.") >> > >> > The reason for removal at the time was that there were IPsec >> > flavoured tunnel types, like ipsec_gre, that consumed for >> > themselves the least significant bit of SKB mark irrelevant >> > whether Open vSwitch users were using IPsec or not. >> > >> > After this patch, there are no IPsec flavored tunnels anymore. >> > IPsec is enabled by setting up the right values in: >> > 1. OVSDB:Interface:options column; >> > 2. OVSDB:Open_vSwitch:other_config column; >> > 3. OpenFlow pipeline. >> > >> > Signed-off-by: Ansis Atteka <aatteka@ovn.org> >> > --- >> >> Have you looked into what it would take to support other systems (for >> instance, BSDs, Fedora/RHEL, CentOS)? > > I've asked Qiuyu to look at RHEL/Fedora support soon. It will take > extra work because Red Hat flavored systems use libreswan instead of > strongswan, and libreswan configuration works differently. Thanks Ben, and Qiuyu.
On Wed, Jun 27, 2018 at 10:58:42AM -0700, Qiuyu Xiao wrote: > From: Ansis Atteka <aatteka@ovn.org> > > This patch reintroduces ovs-monitor-ipsec daemon that > was previously removed by commit 2b02d770 ("openvswitch: > Allow external IPsec tunnel management.") > > The reason for removal at the time was that there were IPsec > flavoured tunnel types, like ipsec_gre, that consumed for > themselves the least significant bit of SKB mark irrelevant > whether Open vSwitch users were using IPsec or not. > > After this patch, there are no IPsec flavored tunnels anymore. > IPsec is enabled by setting up the right values in: > 1. OVSDB:Interface:options column; > 2. OVSDB:Open_vSwitch:other_config column; > 3. OpenFlow pipeline. > > Signed-off-by: Ansis Atteka <aatteka@ovn.org> Thanks for working on this. You should add your own Signed-off-by line just after Ansis's. Aaron gave some helpful comments. I hope you will consider them. The documentation has some odd <p>...</p> paragraphs in it. Do you think that there is missing documentation? Maybe they can just be deleted.
Thanks! I will improve the patch based on the suggestions and will post it soon. -Qiuyu On Tue, Jul 3, 2018 at 1:00 PM, Ben Pfaff <blp@ovn.org> wrote: > On Wed, Jun 27, 2018 at 10:58:42AM -0700, Qiuyu Xiao wrote: >> From: Ansis Atteka <aatteka@ovn.org> >> >> This patch reintroduces ovs-monitor-ipsec daemon that >> was previously removed by commit 2b02d770 ("openvswitch: >> Allow external IPsec tunnel management.") >> >> The reason for removal at the time was that there were IPsec >> flavoured tunnel types, like ipsec_gre, that consumed for >> themselves the least significant bit of SKB mark irrelevant >> whether Open vSwitch users were using IPsec or not. >> >> After this patch, there are no IPsec flavored tunnels anymore. >> IPsec is enabled by setting up the right values in: >> 1. OVSDB:Interface:options column; >> 2. OVSDB:Open_vSwitch:other_config column; >> 3. OpenFlow pipeline. >> >> Signed-off-by: Ansis Atteka <aatteka@ovn.org> > > Thanks for working on this. > > You should add your own Signed-off-by line just after Ansis's. > > Aaron gave some helpful comments. I hope you will consider them. > > The documentation has some odd <p>...</p> paragraphs in it. Do you > think that there is missing documentation? Maybe they can just be > deleted.
diff --git a/Documentation/automake.mk b/Documentation/automake.mk index 2202df45b..3a505924a 100644 --- a/Documentation/automake.mk +++ b/Documentation/automake.mk @@ -59,6 +59,7 @@ DOC_SOURCE = \ Documentation/howto/docker.rst \ Documentation/howto/dpdk.rst \ Documentation/howto/firewalld.rst \ + Documentation/howto/ipsec.rst \ Documentation/howto/kvm.rst \ Documentation/howto/libvirt.rst \ Documentation/howto/selinux.rst \ diff --git a/Documentation/howto/index.rst b/Documentation/howto/index.rst index 201d6936b..9a3487be3 100644 --- a/Documentation/howto/index.rst +++ b/Documentation/howto/index.rst @@ -37,6 +37,7 @@ OVS :maxdepth: 2 kvm + ipsec selinux libvirt ssl diff --git a/Documentation/howto/ipsec.rst b/Documentation/howto/ipsec.rst new file mode 100644 index 000000000..4e4f4d211 --- /dev/null +++ b/Documentation/howto/ipsec.rst @@ -0,0 +1,199 @@ +.. + Licensed under the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain + a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations + under the License. + + Convention for heading levels in Open vSwitch documentation: + + ======= Heading 0 (reserved for the title in a document) + ------- Heading 1 + ~~~~~~~ Heading 2 + +++++++ Heading 3 + ''''''' Heading 4 + + Avoid deeper levels because they do not render well. + +============================================== +How to Encrypt Open vSwitch Tunnels with IPsec +============================================== + +This document describes how to use Open vSwitch integration with strongSwan +5.1 or later to provide IPsec security for STT, GENEVE, GRE and VXLAN tunnels. +This document assumes that you have already installed Open vSwitch. + + +Limitations +----------- + +There are several limitations: + +1) Currently only Debian-based platforms are supported. + +2) There is no backwards compatibility with the old IPsec implementation + that uses Racoon instead of strongSwan for IKE keying. + +3) Some older Open vSwitch datapath kernel modules (in Linux Kernel tree) + do not support route lookups with transport L4 ports properly. In + this case Ethernet over L4 tunneling protocols (e.g. STT, GENEVE) + would not work. However, GRE would still work because it does not + have concept of L4 ports. + +4) Some strongSwan versions might not support certain features. For + example: + + a) AES GCM ciphers that improve performance. + b) xfrm_acq_expires setting in strongSwan configuration file. + This setting tells strongSwan how aggressively to retry + establishing tunnel, if peer did not respond to previous keying + request. + c) set_proto_port_transport_sa in charon configuration file that tells + Linux Kernel to filter out unexpected packets that came over IPsec + tunnel. + d) IPsec transport mode for NAT deployment. + +5) Since IPsec decryption is done from the software when packet has already + gotten past NIC hardware, then benefits of the most tunneling offloads, + like GENEVE or TSO for STT can't be leveraged unless NIC has IPsec offloads + as well and the NIC knows how to leverage them. + + +Setup +----- + +Install strongSwan and openvswitch-ipsec debian packages:: + + $ apt-get install strongswan + $ dpkg -i openvswitch-ipsec_<version>_amd64.deb + + +Configuration +------------- + +IPsec with ovs-monitor-ipsec daemon can be configured in three +different forwarding policy modes: + +1) ovs-monitor-ipsec assumes that packets from all OVS tunnels by default + should be allowed to exit unencrypted unless particular tunnel is + explicitly configured with IPsec parameters:: + + % ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:remote_ip=1.2.3.4 \ + options:psk=swordfish]) + + + The problem with this mode is that there is inherent race condition + between ovs-monitor-ipsec and ovs-vswitchd daemons where ovs-vswitchd + could enable forwarding before ovs-monitor-ipsec actually had a chance + to configure IPsec policies. + This mode should be used only and only if tunnel configuration is static + and/or if there is firewall that can drop the plain packets that + occasionally leak the tunnel unencrypted on OVSDB (re)configuration + events. + + +2) ovs-monitor-ipsec assumes that packets marked with a given SKB mark + must be encrypted and hence should not be allowed to leave the system + unencrypted:: + + % ovs-vsctl set Open_vSwitch . other_config:default_ipsec_skb_mark=1/1 + % ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:remote_ip=1.2.3.4 \ + options:psk=swordfish]) + + With this mode ovs-monitor-ipsec makes strongSwan to install IPsec shunt + policies that serve as safety net and prevent unencrypted tunnel packets + to leave the host in case ovs-vswitchd configured datapath before + ovs-monitor-ipsec installed IPsec policies. + However, assumption here is that OpenFlow controller was careful + and installed OpenFlow rule with set SKB mark action specified in + OVSDB Open_vSwitch table before the first packets were able to leave + the OVS tunnel. + +3) ovs-monitor-ipsec assumes that packets coming from all OVS tunnels + by default need to be protected with IPsec unless the tunnel is explicitly + allowed to leave unencrypted. This is inverse behavior of the second + mode:: + + % ovs-vsctl set Open_vSwitch . other_config:default_ipsec_skb_mark=0/1 + % ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface gre0 type=gre \ + options:remote_ip=1.2.3.4 \ + options:psk=swordfish]) + + With this solution ovs-monitor-ipsec tells strongSwan + to install IPsec shunt policies to match on skb mark 0 + which should be the default skb mark value for all tunnel packets + going through NetFilter/XFRM hooks. As a result IPsec assumes + that all packets coming from tunnels should be encrypted unless + OpenFlow controller explicitly set skb mark to a non-zero value. + This is the most secure mode, but at the same time the most intrusive + one, because the OpenFlow pipeline needs to be overhauled just because + now IPsec is enabled. + + Note that instead of using least significant bit of SKB mark, you could + just as well have used any other bit. + + +Troubleshooting +--------------- + +Use following ovs-apptcl command to get ovs-monitor-ipsec internal +representation of tunnel configuration:: + + % ovs-appctl -t ovs-monitor-ipsec tunnels/show + +If there is misconfiguration then ovs-appctl should indicate why. +For example:: + + Interface name: gre0 v5 (CONFIGURED) <--- Should be set to CONFIGURED. + Otherwise, error message will + be provided + Remote IP: 192.168.2.129 + Tunnel Type: gre + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: None + My key: None + His cert: None + PSK: swordfish + Ofport: 1 <--- Whether ovs-vswitchd has assigned Ofport + number to this Tunnel Port + CFM state: Up <--- Whether CFM declared this tunnel healthy + Kernel policies installed: + ... <--- IPsec policies for this OVS tunnel in + Linux Kernel installed by strongSwan + Kernel security associations installed: + ... <--- IPsec security associations for this OVS + tunnel in Linux Kernel installed by + strongswan + Strongswan connections that are active: + ... <--- strongSwan "connections" for this OVS + tunnel + + +Bug Reporting +------------- + +If you think you may have found a bug with security implications, like + +1) IPsec protected tunnel accepted packets that came unencrypted; OR +2) IPsec protected tunnel allowed packets to leave unencrypted; + +Then report such bugs according to `security guidelines +<Documentation/internals/security.rst>`__. + +If bug does not have security implications, then report it according to +instructions in `<REPORTING-bugs.rst>`__. + +There is also a possibility that there is a bug in strongSwan. In that +case report it to strongSwan mailing list. \ No newline at end of file diff --git a/Makefile.am b/Makefile.am index e02799a90..c0fef11fd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -480,6 +480,7 @@ include tests/automake.mk include include/automake.mk include third-party/automake.mk include debian/automake.mk +include ipsec/automake.mk include vswitchd/automake.mk include ovsdb/automake.mk include rhel/automake.mk diff --git a/debian/automake.mk b/debian/automake.mk index 4d8e204bb..8a8d43c9f 100644 --- a/debian/automake.mk +++ b/debian/automake.mk @@ -20,6 +20,9 @@ EXTRA_DIST += \ debian/openvswitch-datapath-source.copyright \ debian/openvswitch-datapath-source.dirs \ debian/openvswitch-datapath-source.install \ + debian/openvswitch-ipsec.dirs \ + debian/openvswitch-ipsec.init \ + debian/openvswitch-ipsec.install \ debian/openvswitch-pki.dirs \ debian/openvswitch-pki.postinst \ debian/openvswitch-pki.postrm \ diff --git a/debian/control b/debian/control index a4c031d85..9443e91c9 100644 --- a/debian/control +++ b/debian/control @@ -320,3 +320,24 @@ Description: Open vSwitch development package 1000V. . This package provides openvswitch headers and libopenvswitch for developers. + +Package: openvswitch-ipsec +Architecture: linux-any +Depends: iproute2, + openvswitch-common (= ${binary:Version}), + openvswitch-switch (= ${binary:Version}), + python, + python-openvswitch (= ${source:Version}), + strongswan, + ${misc:Depends}, + ${shlibs:Depends} +Description: Open vSwitch IPsec tunneling support + Open vSwitch is a production quality, multilayer, software-based, + Ethernet virtual switch. It is designed to enable massive network + automation through programmatic extension, while still supporting + standard management interfaces and protocols (e.g. NetFlow, IPFIX, + sFlow, SPAN, RSPAN, CLI, LACP, 802.1ag). In addition, it is designed + to support distribution across multiple physical servers similar to + VMware's vNetwork distributed vswitch or Cisco's Nexus 1000V. + . + This package provides IPsec tunneling support for OVS tunnels. diff --git a/debian/openvswitch-ipsec.dirs b/debian/openvswitch-ipsec.dirs new file mode 100644 index 000000000..fca44aa7b --- /dev/null +++ b/debian/openvswitch-ipsec.dirs @@ -0,0 +1 @@ +usr/share/openvswitch/scripts \ No newline at end of file diff --git a/debian/openvswitch-ipsec.init b/debian/openvswitch-ipsec.init new file mode 100644 index 000000000..1a9683785 --- /dev/null +++ b/debian/openvswitch-ipsec.init @@ -0,0 +1,189 @@ +#!/bin/sh +# +# Copyright (c) 2007, 2009 Javier Fernandez-Sanguino <jfs@debian.org> +# +# This is free software; you may redistribute it and/or modify +# it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2, +# or (at your option) any later version. +# +# This is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License with +# the Debian operating system, in /usr/share/common-licenses/GPL; if +# not, write to the Free Software Foundation, Inc., 59 Temple Place, +# Suite 330, Boston, MA 02111-1307 USA +# +### BEGIN INIT INFO +# Provides: openvswitch-ipsec +# Required-Start: $network $local_fs $remote_fs openvswitch-switch +# Required-Stop: $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Open vSwitch GRE-over-IPsec daemon +# Description: The ovs-monitor-ipsec script provides support for encrypting GRE +# tunnels with IPsec. +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +DAEMON=/usr/share/openvswitch/scripts/ovs-monitor-ipsec # Daemon's location +NAME=ovs-monitor-ipsec # Introduce the short server's name here +LOGDIR=/var/log/openvswitch # Log directory to use + +PIDFILE=/var/run/openvswitch/$NAME.pid + +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +DODTIME=10 # Time to wait for the server to die, in seconds + # If this value is set too low you might not + # let some servers to die gracefully and + # 'restart' will not work + +set -e + +running_pid() { +# Check if a given process pid's cmdline matches a given name + pid=$1 + name=$2 + [ -z "$pid" ] && return 1 + [ ! -d /proc/$pid ] && return 1 + cmd=`cat /proc/$pid/cmdline | tr "\000" " "|cut -d " " -f 2` + # Is this the expected server + [ "$cmd" != "$name" ] && return 1 + return 0 +} + +running() { +# Check if the process is running looking at /proc +# (works for all users) + + # No pidfile, probably no daemon present + [ ! -f "$PIDFILE" ] && return 1 + pid=`cat $PIDFILE` + running_pid $pid $DAEMON || return 1 + return 0 +} + +start_server() { + if [ ! -d /var/run/openvswitch ]; then + install -d -m 755 -o root -g root /var/run/openvswitch + fi + + /usr/share/openvswitch/scripts/ovs-monitor-ipsec \ + --pidfile=$PIDFILE --log-file --detach --monitor \ + unix:/var/run/openvswitch/db.sock + + return 0 +} + +stop_server() { + if [ -e $PIDFILE ]; then + kill `cat $PIDFILE` + fi + + return 0 +} + +force_stop() { +# Force the process to die killing it manually + [ ! -e "$PIDFILE" ] && return + if running ; then + kill -15 $pid + # Is it really dead? + sleep "$DODTIME" + if running ; then + kill -9 $pid + sleep "$DODTIME" + if running ; then + echo "Cannot kill $NAME (pid=$pid)!" + exit 1 + fi + fi + fi + rm -f $PIDFILE +} + + +case "$1" in + start) + log_daemon_msg "Starting $NAME" + # Check if it's running first + if running ; then + log_progress_msg "apparently already running" + log_end_msg 0 + exit 0 + fi + if start_server && running ; then + # It's ok, the server started and is running + log_end_msg 0 + else + # Either we could not start it or it is not running + # after we did + # NOTE: Some servers might die some time after they start, + # this code does not try to detect this and might give + # a false positive (use 'status' for that) + log_end_msg 1 + fi + ;; + stop) + log_daemon_msg "Stopping $NAME" + if running ; then + # Only stop the server if we see it running + stop_server + log_end_msg $? + else + # If it's not running don't do anything + log_progress_msg "apparently not running" + log_end_msg 0 + exit 0 + fi + ;; + force-stop) + # First try to stop gracefully the program + $0 stop + if running; then + # If it's still running try to kill it more forcefully + log_daemon_msg "Stopping (force) $NAME" + force_stop + log_end_msg $? + fi + ;; + restart|force-reload) + log_daemon_msg "Restarting $NAME" + stop_server + # Wait some sensible amount, some server need this + [ -n "$DODTIME" ] && sleep $DODTIME + start_server + running + log_end_msg $? + ;; + status) + log_daemon_msg "Checking status of $NAME" + if running ; then + log_progress_msg "running" + log_end_msg 0 + else + log_progress_msg "apparently not running" + log_end_msg 1 + exit 1 + fi + ;; + # Use this if the daemon cannot reload + reload) + log_warning_msg "Reloading $NAME daemon: not implemented, as the daemon" + log_warning_msg "cannot re-read the config file (use restart)." + ;; + *) + N=/etc/init.d/openvswitch-ipsec + echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2 + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/debian/openvswitch-ipsec.install b/debian/openvswitch-ipsec.install new file mode 100644 index 000000000..8fe665cb3 --- /dev/null +++ b/debian/openvswitch-ipsec.install @@ -0,0 +1 @@ +ipsec/ovs-monitor-ipsec usr/share/openvswitch/scripts diff --git a/ipsec/automake.mk b/ipsec/automake.mk new file mode 100644 index 000000000..12baaed55 --- /dev/null +++ b/ipsec/automake.mk @@ -0,0 +1,9 @@ +# Copyright (C) 2017 Nicira, Inc. +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without warranty of any kind. + +EXTRA_DIST += \ + ipsec/ovs-monitor-ipsec diff --git a/ipsec/ovs-monitor-ipsec b/ipsec/ovs-monitor-ipsec new file mode 100755 index 000000000..322a7045a --- /dev/null +++ b/ipsec/ovs-monitor-ipsec @@ -0,0 +1,727 @@ +#!/usr/bin/python +# Copyright (c) 2017 Nicira, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import glob +import os +import re +import subprocess +import sys +from string import Template + +from ovs.db import error +from ovs.db import types +import ovs.daemon +import ovs.db.idl +import ovs.dirs +import ovs.unixctl +import ovs.unixctl.server +import ovs.util +import ovs.vlog + + +FILE_HEADER = "# Generated by ovs-monitor-ipsec...do not modify by hand!\n\n" + +vlog = ovs.vlog.Vlog("ovs-monitor-ipsec") +exiting = False +keyer = None +xfrm = None + + +def unixctl_xfrm_policies(conn, unused_argv, unused_aux): + global xfrm + policies = xfrm.get_policies() + conn.reply(str(policies)) + +def unixctl_xfrm_state(conn, unused_argv, unused_aux): + global xfrm + securities = xfrm.get_securities() + conn.reply(str(securities)) + +def unixctl_ipsec_status(conn, unused_argv, unused_aux): + global keyer + conns = keyer._get_strongswan_conns() + conn.reply(str(conns)) + +def unixctl_show(conn, unused_argv, unused_aux): + global keyer + global xfrm + policies = xfrm.get_policies() + securities = xfrm.get_securities() + keyer.show(conn, policies, securities) + +def unixctl_exit(conn, unused_argv, unused_aux): + global exiting + exiting = True + conn.reply(None) + + +class XFRM(object): + """This class is a simple wrapper around ip-xfrm (8) command line + utility. We are using this class only for informational purposes + so that ovs-monitor-ipsec could verify that IKE keying daemon has + installed IPsec policies and security associations into kernel as + expected.""" + + def __init__(self, ip_root_prefix): + self.IP = ip_root_prefix + "/sbin/ip" + + def get_policies(self): + """This function returns IPsec policies (from kernel) in a dictionary + where <key> is destination IPv4 address and <value> is SELECTOR of + the IPsec policy.""" + policies = {} + proc = subprocess.Popen([self.IP, 'xfrm', 'policy'], + stdout=subprocess.PIPE) + while True: + line = proc.stdout.readline().strip() + if line == '': + break + a = line.split(" ") + if len(a) >= 4 and a[0] == "src" and a[2] == "dst": + dst = (a[3].split("/"))[0] + if not dst in policies: + policies[dst] = [] + policies[dst].append(line) + src = (a[3].split("/"))[0] + if not src in policies: + policies[src] = [] + policies[src].append(line) + return policies + + def get_securities(self): + """This function returns IPsec security associations (from kernel) + in a dictionary where <key> is destination IPv4 address and <value> + is SELECTOR.""" + securities = {} + proc = subprocess.Popen([self.IP, 'xfrm', 'state'], + stdout=subprocess.PIPE) + while True: + line = proc.stdout.readline().strip() + if line == '': + break + a = line.split(" ") + if len(a) >= 4 and a[0] == "sel" and a[1] == "src" and a[3] == "dst": + remote_ip = a[4].rstrip().split("/")[0] + local_ip = a[2].rstrip().split("/")[0] + if not remote_ip in securities: + securities[remote_ip] = [] + securities[remote_ip].append(line) + if not local_ip in securities: + securities[local_ip] = [] + securities[local_ip].append(line) + return securities + + +class StrongSwanTunnel(object): + """This class represents IPsec tunnel in strongSwan.""" + + transp_tmpl = {"gre" : Template("""\ +conn $ifname-$version +$auth_section + leftsubnet=%dynamic[gre] + rightsubnet=%dynamic[gre] + +"""), "gre64" : Template("""\ +conn $ifname-$version +$auth_section + leftsubnet=%dynamic[gre] + rightsubnet=%dynamic[gre] + +"""), "geneve" : Template("""\ +conn $ifname-in-$version +$auth_section + rightsubnet=%dynamic[udp/%any] + leftsubnet=%dynamic[udp/6081] + +conn $ifname-out-$version +$auth_section + rightsubnet=%dynamic[udp/6081] + leftsubnet=%dynamic[udp/%any] + +"""), "stt" : Template("""\ +conn $ifname-in-$version +$auth_section + rightsubnet=%dynamic[tcp/%any] + leftsubnet=%dynamic[tcp/7471] + +conn $ifname-out-$version +$auth_section + rightsubnet=%dynamic[tcp/7471] + leftsubnet=%dynamic[tcp/%any] + +"""), "vxlan" : Template("""\ +conn $ifname-in-$version +$auth_section + rightsubnet=%dynamic[udp/%any] + leftsubnet=%dynamic[udp/4789] + +conn $ifname-out-$version +$auth_section + rightsubnet=%dynamic[udp/4789] + leftsubnet=%dynamic[udp/%any] + +""")} + + auth_tmpl = {"psk" : Template("""\ + left=$local_ip + right=$remote_ip + authby=psk"""), + "rsa" : Template("""\ + left=$local_ip + right=$remote_ip + rightcert=ovs-$remote_ip.pem + leftcert=$certificate""")} + + unixctl_config_tmpl = Template("""\ + Remote IP: $remote_ip + Tunnel Type: $tunnel_type + Local IP: $local_ip + SKB mark: $skb_mark + Use SSL cert: $use_ssl_cert + My cert: $certificate + My key: $private_key + His cert: $peer_cert + PSK: $psk +""") + + unixctl_status_tmpl = Template("""\ + Ofport: $ofport + CFM state: $cfm_state +""") + + def __init__(self, name, row): + self.name = name # 'name' will not change because it is key in OVSDB + self.version = 0 # 'version' is increased on configuration changes + self.last_refreshed_version = -1 + self.state = "INIT" + self.conf = {} + self.status = {} + self.cert_file = None + self.update_conf(row) + + def update_conf(self, row): + """This function updates IPsec tunnel configuration by using 'row' + from OVSDB interface table. If configuration was actually changed + in OVSDB then this function returns True. Otherwise, it returns + False.""" + ret = False + options = row.options + use_ssl_cert = options.get("use_ssl_cert") == "true" + cert = options.get("certificate") + key = options.get("private_key") + if use_ssl_cert: # Override with SSL certs if told so + (cert, key) = (keyer.public_cert, keyer.private_key) + + new_conf = { + "ifname" : self.name, + "tunnel_type" : row.type, + "remote_ip" : options.get("remote_ip"), + "local_ip" : options.get("local_ip", "0.0.0.0"), + "skb_mark" : keyer.wanted_skb_mark, + "certificate" : cert, + "private_key" : key, + "use_ssl_cert" : use_ssl_cert, + "peer_cert" : options.get("peer_cert"), + "psk" : options.get("psk")} + if self.conf != new_conf: + # Configuration was updated in OVSDB. Validate it and figure + # out what to do next with this IPsec tunnel. Also, increment + # version number of this IPsec tunnel so that we could tell + # apart old and new tunnels in "ipsec status" output. + self.version += 1 + ret = True + self.conf = new_conf + if self._validate_conf(): + self.state = "CONFIGURED" + else: + vlog.warn("%s contains invalid configuration%s" % + (self.name, self.invalid_reason)) + self.state = "INVALID" + + new_status = { + "cfm_state" : "Up" if row.cfm_fault == [False] else + "Down" if row.cfm_fault == [True] else + "Disabled", + "ofport" : "Not assigned" if (row.ofport in [[], [-1]]) else + row.ofport[0]} + if self.status != new_status: + # Tunnel has become unhealthy or ofport changed. Simply log this. + vlog.dbg("%s changed status from %s to %s" % + (self.name, str(self.status), str(new_status))) + self.status = new_status + return ret + + def mark_for_removal(self): + """This function marks tunnel for removal. We can't delete tunnel + right away because we have to clean any files created for this tunnel + from it's run() function.""" + self.version += 1 + self.state = "REMOVED" + + def run(self): + """This function ensures that Tunnel's Finite State Machine moves + forward.""" + if self.last_refreshed_version == self.version: + # Configuration hasn't changed. Exit early. + return False + + if self.state == "CONFIGURED": + # If configuration changed then cleanup the old state + # and push the new state + self._cleanup_old_state() + self._push_new_state() + elif self.state == "INVALID": + # If configuration is invalid then we prevent this + # tunnel from pushing any configuration to the system + self._cleanup_old_state() + elif self.state == "REMOVED": + self._cleanup_old_state() + else: + vlog.fatal("%s is in unexpected state" % self.name) + + self.last_refreshed_version = self.version + return True + + def write_config(self, secrets, conf): + if self.conf["psk"]: + secrets.write("%s : PSK %s\n" % (self.conf["remote_ip"], + self.conf["psk"])) + auth_section = self.auth_tmpl["psk"].substitute(self.conf) + else: + secrets.write("%s : RSA %s\n" % (self.conf["remote_ip"], + self.conf["private_key"])) + auth_section = self.auth_tmpl["rsa"].substitute(self.conf) + vals = self.conf.copy() + vals["auth_section"] = auth_section + vals["version"] = self.version + conf.write(self.transp_tmpl[self.conf["tunnel_type"]].substitute(vals)) + + def show(self, policies, securities, conns): + state = self.state + if self.state == "INVALID": + state += self.invalid_reason + header = "Interface name: %s v%u (%s)\n" % (self.name, self.version, + state) + conf = self.unixctl_config_tmpl.substitute(self.conf) + status = self.unixctl_status_tmpl.substitute(self.status) + spds = "Kernel policies installed:\n" + remote_ip = self.conf["remote_ip"] + if remote_ip in policies: + for line in policies[remote_ip]: + spds += " " + line + "\n" + sas = "Kernel security associations installed:\n" + if remote_ip in securities: + for line in securities[remote_ip]: + sas += " " + line + "\n" + cons = "Strongswan connections that are active:\n" + if self.name in conns: + for tname in conns[self.name]: + cons += " " + conns[self.name][tname] + "\n" + + return header + conf + status + spds + sas + cons + "\n" + + def _validate_conf(self): + """This function verifies if IPsec tunnel has valid configuration + set in 'conf'. If it is valid, then it returns True. Otherwise, + it returns False and sets the reason why configuration was considered + as invalid. + + This function could be improved in future to also verify validness + of certificates themselves so that ovs-monitor-ipsec would not + pass malformed configuration to strongSwan.""" + + self.invalid_reason = None + if not self.conf["remote_ip"]: + self.invalid_reason = ": 'remote_ip' is not set" + elif self.conf["peer_cert"]: + if self.conf["psk"]: + self.invalid_reason = ": 'psk' must be unset with PKI" + elif not self.conf["certificate"]: + self.invalid_reason = ": must set 'certificate' with PKI" + elif not self.conf["private_key"]: + self.invalid_reason = ": must set 'private_key' with PKI" + elif self.conf["psk"]: + if self.conf["certificate"] or self.conf["private_key"]: + self.invalid_reason = ": 'certificate', 'private_key' and "\ + "'use_ssl_cert' must be unset with PSK" + if self.invalid_reason: + return False + return True + + def _cleanup_old_state(self): + if self.cert_file: + try: + os.remove(self.cert_file) + except OSError: + vlog.warn("could not remove '%s'" % (self.cert_file)) + self.cert_file = None + + def _push_new_state(self): + if self.conf["peer_cert"]: + fn = keyer.CERT_DIR + "/ovs-%s.pem" % (self.name) + try: + cert = open(fn, "w") + try: + cert.write(self.conf["peer_cert"]) + finally: + cert.close() + vlog.info("created peer certificate %s" % (fn)) + self.cert_file = fn + except (OSError, IOError) as e: + vlog.err("could not create certificate '%s'" % (fn)) + + +class StrongSwanKeyer(object): + """This class is a wrapper around strongSwan.""" + + STRONGSWAN_CONF = """%s +charon.plugins.kernel-netlink.set_proto_port_transport_sa = yes +charon.plugins.kernel-netlink.xfrm_ack_expires = 10 +""" % (FILE_HEADER) + CONF_HEADER = """%s +config setup + uniqueids=no + +conn %%default + keyingtries=%%forever + type=transport + keyexchange=ikev2 + auto=route + ike=aes128gcm12-aesxcbc-modp1024 + esp=aes128gcm12-modp1024 + +""" % (FILE_HEADER) + SHUNT_POLICY = """conn prevent_unencrypted_gre + type=drop + leftprotoport=gre + mark=%s + +conn prevent_unencrypted_stt + type=drop + leftprotoport=tcp/7471 + mark=%s + +""" + + def __init__(self, strongswan_root_prefix): + self.private_key = None + self.public_cert = None + self.wanted_skb_mark = None + self.in_use_skb_mark = None + self.tunnels = {} + self.CERT_DIR = strongswan_root_prefix + "/etc/ipsec.d/certs" + self.CHARON_CONF = strongswan_root_prefix + "/etc/strongswan.d/ovs.conf" + self.IPSEC = strongswan_root_prefix + "/usr/sbin/ipsec" + self.IPSEC_CONF = strongswan_root_prefix + "/etc/ipsec.conf" + self.IPSEC_SECRETS = strongswan_root_prefix + "/etc/ipsec.secrets" + + def is_tunneling_type_supported(self, tunnel_type): + """Returns True if we know how to configure IPsec for these + types of tunnels. Otherwise, returns False. """ + return tunnel_type in StrongSwanTunnel.transp_tmpl + + def is_ipsec_required(self, options_column): + """Return True if tunnel needs to be encrypted. Otherwise, + returns False.""" + return "psk" in options_column or "peer_cert" in options_column + + def restart(self): + """This function restarts strongSwan.""" + self._initial_configuration() + vlog.info("restarting strongSwan") + subprocess.call([self.IPSEC, "restart"]) + + def add_tunnel(self, name, row): + """Adds a new tunnel that keyer will provision with 'name'.""" + vlog.info("Tunnel %s appeared in OVSDB" % (name)) + self.tunnels[name] = StrongSwanTunnel(name, row) + + def update_tunnel(self, name, row): + """Updates configuration of already existing tunnel with 'name'.""" + tunnel = self.tunnels[name] + if tunnel.update_conf(row): + vlog.info("Tunnel's '%s' configuration changed in OVSDB to %u" % + (tunnel.name, tunnel.version)) + + def del_tunnel(self, name): + """Deletes tunnel by 'name'.""" + vlog.info("Tunnel %s disappeared from OVSDB" % (name)) + self.tunnels[name].mark_for_removal() + + def update_ssl_credentials(self, credentials): + """Updates already existing """ + self.public_cert = credentials[0] + self.private_key = credentials[1] + + def update_skb_mark(self, skb_mark): + """Update skb_mark used in IPsec policies.""" + self.wanted_skb_mark = skb_mark + + def show(self, unix_conn, policies, securities): + """This function prints all tunnel state in 'unix_conn'. + It uses 'policies' and securities' received from Linux Kernel + to show if tunnels were actually configured by strongSwan.""" + if not self.tunnels: + unix_conn.reply("No tunnels configured with IPsec") + return + s = "" + conns = self._get_strongswan_conns() + for name, tunnel in self.tunnels.iteritems(): + s += tunnel.show(policies, securities, conns) + unix_conn.reply(s) + + def run(self): + """This function runs state machine that represents whole + strongSwan configuration (i.e. merged together from individual + tunnel state machines). It creates configuration files and + tells strongSwan to update configuration.""" + needs_refresh = False + removed_tunnels = [] + ipsec_secrets = open(self.IPSEC_SECRETS, "w") + ipsec_conf = open(self.IPSEC_CONF, "w") + ipsec_secrets.write(FILE_HEADER) + ipsec_conf.write(self.CONF_HEADER) + if self.in_use_skb_mark != self.wanted_skb_mark: + self.in_use_skb_mark = self.wanted_skb_mark + needs_refresh = True + + if self.in_use_skb_mark: + ipsec_conf.write(" mark_out=%s\n" % self.in_use_skb_mark) + ipsec_conf.write(self.SHUNT_POLICY % (self.in_use_skb_mark, self.in_use_skb_mark)) + + for name, tunnel in self.tunnels.iteritems(): + if tunnel.run(): + needs_refresh = True + + if tunnel.state == "REMOVED": + removed_tunnels.append(name) + elif tunnel.state == "CONFIGURED": + tunnel.write_config(ipsec_secrets, ipsec_conf) + ipsec_secrets.close() + ipsec_conf.close() + + for name in removed_tunnels: + del self.tunnels[name] + + if needs_refresh: + self._refresh() + + def _refresh(self): + """This functions refreshes strongSwan configuration. Behind the + scenes this function calls: + 1. once "ipsec update" command that tells strongSwan to load + all new tunnels from "ipsec.conf"; and + 2. once "ipsec rereadsecrets" command that tells strongswan to load + secrets from "ipsec.conf" file + 3. for every removed tunnel "ipsec stroke down-nb <tunnel>" command + that removes old tunnels. + Once strongSwan vici bindings will be distributed with major + Linux distributions this function could be simplified.""" + vlog.info("Refreshing strongSwan configuration") + subprocess.call([self.IPSEC, "update"]) + subprocess.call([self.IPSEC, "rereadsecrets"]) + + # "ipsec update" command does not remove those tunnels that were + # updated or that disappeared from the ipsec.conf file. So, we have + # to manually remove them by calling "ipsec stroke down-nb <tunnel>" + # command. We use <version> number to tell apart tunnels that + # were just updated. + # "ipsec down-nb" command is designed to be non-blocking (opposed + # to "ipsec down" command). This means that we should not be concerned + # about possibility of ovs-monitor-ipsec to block for each tunnel + # while strongSwan sends IKE messages over Internet. + conns_dict = self._get_strongswan_conns() + for ifname, conns in conns_dict.iteritems(): + tunnel = self.tunnels.get(ifname) + for conn in conns: + # IPsec "connection" names that we choose in strongswan + # must start with Interface name + if not conn.startswith(ifname): + vlog.err("%s does not start with %s" % (conn, ifname)) + continue + + # version number should be the first integer after + # interface name in IPsec "connection" + try: + ver = int(re.findall(r'\d+', conn[len(ifname):])[0]) + except ValueError, IndexError: + vlog.err("%s does not contain version number") + continue + + if not tunnel or tunnel.version != ver: + vlog.info("%s is outdated %u" % (conn, ver)) + subprocess.call([self.IPSEC, "stroke", "down-nb", conn]) + + + def _get_strongswan_conns(self): + """This function parses output from 'ipsec status' command. + It returns dictionary where <key> is interface name (as in OVSDB) + and <value> is another dictionary. This another dictionary + uses strongSwan connection name as <key> and more detailed + sample line from the parsed outpus as <value>. """ + + conns = {} + proc = subprocess.Popen([self.IPSEC, 'status'], + stdout=subprocess.PIPE) + while True: + line = proc.stdout.readline().strip() + if line == '': + break + tunnel_name = line.split(":") + if len(tunnel_name) < 2: + continue + ifname = tunnel_name[0].split("-") + if len(ifname) < 2: + continue + + if not ifname[0] in conns: + conns[ifname[0]] = {} + (conns[ifname[0]])[tunnel_name[0]] = line + + return conns + + def _initial_configuration(self): + """This function creates initial configuration that strongSwan should + receive on startup.""" + f = open(self.CHARON_CONF, "w") + f.write(self.STRONGSWAN_CONF) + f.close() + + f = open(self.IPSEC_CONF, "w") + f.write(self.CONF_HEADER) + f.close() + + f = open(self.IPSEC_SECRETS, "w") + f.write(FILE_HEADER) + f.close() + + +def read_ovsdb_open_vswitch_table(data): + """This functions reads IPsec relevant configuration from Open_vSwitch + table.""" + ssl = (None, None) + global_ipsec_skb_mark = None + for row in data["Open_vSwitch"].rows.itervalues(): + if row.ssl: + ssl = (row.ssl[0].certificate, row.ssl[0].private_key) + global_ipsec_skb_mark = row.other_config.get("default_ipsec_skb_mark") + keyer.update_ssl_credentials(ssl) + keyer.update_skb_mark(global_ipsec_skb_mark) + +def read_ovsdb_interface_table(data): + """This function reads the IPsec relevant configuration from Interface + table.""" + ifaces = set() + for row in data["Interface"].rows.itervalues(): + if not keyer.is_tunneling_type_supported(row.type): + continue + if not keyer.is_ipsec_required(row.options): + continue + if row.name in keyer.tunnels: + keyer.update_tunnel(row.name, row) + else: + keyer.add_tunnel(row.name, row) + ifaces.add(row.name) + # Mark for removal those tunnels that just disappeared from OVSDB + for tunnel in keyer.tunnels.keys(): + if not tunnel in ifaces: + keyer.del_tunnel(tunnel) + +def read_ovsdb(data): + """This function reads all configuration from OVSDB that ovs-monitor-ipsec + is interested in.""" + read_ovsdb_open_vswitch_table(data) + read_ovsdb_interface_table(data) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("database", metavar="DATABASE", + help="A socket on which ovsdb-server is listening.") + parser.add_argument("--root-prefix", metavar="DIR", + help="Use DIR as alternate root directory" + " (for testing).") + + ovs.vlog.add_args(parser) + ovs.daemon.add_args(parser) + args = parser.parse_args() + ovs.vlog.handle_args(args) + ovs.daemon.handle_args(args) + + global keyer + global xfrm + + root_prefix = args.root_prefix if args.root_prefix else "" + xfrm = XFRM(root_prefix) + keyer = StrongSwanKeyer(root_prefix) + + remote = args.database + schema_helper = ovs.db.idl.SchemaHelper() + schema_helper.register_columns("Interface", + ["name", "type", "options", "cfm_fault", + "ofport"]) + schema_helper.register_columns("Open_vSwitch", ["ssl", "other_config"]) + schema_helper.register_columns("SSL", ["certificate", "private_key"]) + idl = ovs.db.idl.Idl(remote, schema_helper) + + ovs.daemon.daemonize() + + ovs.unixctl.command_register("xfrm/policies", "", 0, 0, + unixctl_xfrm_policies, None) + ovs.unixctl.command_register("xfrm/state", "", 0, 0, + unixctl_xfrm_state, None) + ovs.unixctl.command_register("ipsec/status", "", 0, 0, + unixctl_ipsec_status, None) + ovs.unixctl.command_register("tunnels/show", "", 0, 0, + unixctl_show, None) + ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None) + + error, unixctl_server = ovs.unixctl.server.UnixctlServer.create(None) + if error: + ovs.util.ovs_fatal(error, "could not create unixctl server", vlog) + + seqno = idl.change_seqno # Sequence number when OVSDB was processed last time + keyer.restart() + while True: + unixctl_server.run() + if exiting: + break + + idl.run() + if seqno != idl.change_seqno: + read_ovsdb(idl.tables) + seqno = idl.change_seqno + + keyer.run() + + poller = ovs.poller.Poller() + unixctl_server.wait(poller) + idl.wait(poller) + poller.block() + + unixctl_server.close() + idl.close() + + +if __name__ == '__main__': + try: + main() + except SystemExit: + # Let system.exit() calls complete normally + raise + except: + vlog.exception("traceback") + sys.exit(ovs.daemon.RESTART_EXIT_CODE) diff --git a/tests/automake.mk b/tests/automake.mk index 8224e5a4a..703b4fa50 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -60,6 +60,7 @@ TESTSUITE_AT = \ tests/lockfile.at \ tests/reconnect.at \ tests/ovs-vswitchd.at \ + tests/ovs-monitor-ipsec.at \ tests/dpif-netdev.at \ tests/dpctl.at \ tests/ofproto-dpif.at \ diff --git a/tests/ovs-monitor-ipsec.at b/tests/ovs-monitor-ipsec.at new file mode 100644 index 000000000..ec537ad32 --- /dev/null +++ b/tests/ovs-monitor-ipsec.at @@ -0,0 +1,1076 @@ +# WAIT_FOR_TUNNEL_TO_UPDATE([tunnel], [version], [state]) +# +# This macro should be called after ovs-vsctl command that updated +# IPsec configuration in OVSDB. It basically serves as synchronization +# barrier to ensures that ovs-monitor-ipsec daemon's internal configuration +# for 'tunnel' has updated to 'version' and is in 'state'. +m4_define([WAIT_FOR_TUNNEL_TO_UPDATE], [ +OVS_WAIT_UNTIL([ovs-appctl -t ovs-monitor-ipsec tunnels/show | grep "Interface name: $1 v$2 ($3"]) +]) + + +# WAIT_FOR_PROPER_IPSEC_CLEANUP([]) +# +# This macro should be called *only* after all IPsec tunnels were removed +# from OVSDB Interface table. This macro verifies that: +# 1) ovs-monitor-ipsec daemon thinks that there are no IPsec tunnels +# 2) ipsec.conf file does not contain any tunnels +# 3) ipsec.secrets does not contain any 'secrets' for any tunnels +# 4) ipsec.d directory does not contain any certificates that were +# previously created by ovs-monitor-ipsec daemon +m4_define([WAIT_FOR_PROPER_IPSEC_CLEANUP], [ +OVS_WAIT_UNTIL([ovs-appctl -t ovs-monitor-ipsec tunnels/show | grep "No tunnels configured with IPsec"]) +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + +]) +AT_CHECK([cat etc/ipsec.conf], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + + +config setup + uniqueids=no + +conn %default + keyingtries=%forever + type=transport + keyexchange=ikev2 + auto=route + mark_in=1/1 + ike=aes128gcm12-aesxcbc-modp1024 + esp=aes128gcm12-modp1024 + +]) +AT_CHECK([ls etc/ipsec.d/certs], [0], [dnl]) +]) + + +# VERIFY_IPSEC_COMMANDS_EXECUTED([ipsec_commands]) +# +# This macro alows to verify that ovs-monitor-ipsec daemon called correct +# 'ipsec' commands. All ipsec commands are logged by a script that mocks +# 'ipsec' utility. +# This macro filters out "ipsec rereadsecrets" and "ipsec status" +# commands. +m4_define([VERIFY_IPSEC_COMMANDS_EXECUTED], [ +AT_CHECK([egrep -v "ipsec status|ipsec rereadsecrets" actions], [0], [$1]) +]) + + +# INIT_SSL_CERTS([]) +# +# This macro creates sample SSL certificates so that it would be +# possible to test tunnels that use "use_ssl_certs" setting. Don't +# use these certificates in production. +m4_define([INIT_SSL_CERTS], [ +AT_DATA([ssl_key.pem], [ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAxd9ZkUK+sBToEb8x1LSoMfVPw+l4ww915BNn/0TSA7yY+lq/ +k14+FYHFdqB9vSh5C0bUdhrE7QJ451WP8NyTDIUuZlieUBzonXy0hYVBQ8bUSGfF +go/4IWb/tHKIlTiZ86GqaBc8uddiZ7cF2beZwv9un9ZHOGtQrvMdzB7FzmzZraJ1 +NbAsr1GX4w0euBjPbUyu96nzBeKWqX3vdERK1ddL5y8hA2kSUslci60zCf9/jvcV +aSDvcpLwxKq2QYeS8caPWLQmyszyBrdIvsnrTcUPIG+OOs/w3M+KNYKrySF12hKU +bUujIrBPt+RJ9ptkv28TYRYqnigRZF3nzNnjewIDAQABAoIBAQCqWh2cZ6APrBAX +p0lZXKcpS47+lbQ4CsluMB7qr+829FmnwBUK5KoCjhTYild2UK/VO4eSnn1Hp0c7 +sngX325h6w8FYen6AslpPIGWKiEEHtuH8n8iZpwy9Z/TVH+uKGqyS06QCuFnBb1c +mT9aLy0bqhktVqc+NXXjCL9wilW13pAMh56zYQ8oMJUvNLLknttv9dYmuIyZ9hg/ +5al4pN7OqzC9N4AAgDjyHIn3N6t6yfBc/kcJ9OdJ1709DYIr6Nl500Nhf+GZkrEX +1KTeWPzV6GQD7FUZTJ5eZWNnLbD03823Q0HCElGQkboCUU65EdVdNEHvGdPZw74d +t2iaXPxxAoGBAPGvd+PByAH98KKeu9z5ysc0AgVOIDTlqhReqMc2jnYTCglWlMcx +al7K0T6QcSTl3Y2xgGo5O7P+MzZkIiKFPx5fX2d9x6CybEcOQ+5bd6fI0hYF8W18 +i+i/mmJgGo33UOW6EQfYKAHLn4WdlsKJ9aTaNNCanqkqmS1l+GsrRgYdAoGBANGX +kl/kfGVYOSRh6ztnRc0i3ONo4RZrDgV0bjEmmujc7V5CIDPxhMUkpsOcG8iCt+55 +4JqdtLbmfWfeTWWafn8j/Y9DsSv4U0MHc6+Hfit1dmYmZ+ixHjzul9m6H7XsYZ2k +IpNa7RN+MqvB66pHGpTcC8IQ2fNIkK3GtWLaA3x3AoGBAOX90xtcZxbuLzax05jf +5MZYiau+wwtTmtyzj+2zzzIxwBVO3VoJfm4il6jwD5vLW2Dhj5CGUnhg6R9TfuBW +6M/gdounuHcGE+AyhRao2F9EzhfDJBLKuOGOpD4Fsn9y4Psca+SJINlEitO+OZ97 +ZdWxCR2SZnYZYZdAOHzTu1lJAoGAJcxK9oYzNOerLneGP6lJOkx+P3jLlwppdexg +bvbCWxp0qFoOiq+UvST1+jLuA8QnPZe3PMsSKyX4GcJKfPdWtsEb2jlf+0kGYwE2 +CMLLqzS8zIFCngFLLbvtoLNjQqDFnfNa1O5B8RECPF11jbjS/2OLr0zwsWI1zVEX +pyMgG9MCgYBb+PUIVpo5vRMTsyQ5whRQPK7+5jbU+wFKMnryqfsZ/K6DfeoZzAmn +KhjeRveOiYEfQZfI5GSfm97TKX5hYKQSwOHa9yzTD25iw4aleU8hc4IbqANzbr/c +59KynTtKfZXSb/DAd/FTtFvzfwiin5ss4INKbpuBACDrb+mj1nY6bw== +-----END RSA PRIVATE KEY----- +]) +AT_DATA([ssl_cert.pem], [ +-----BEGIN CERTIFICATE----- +MIIDgjCCAmoCAQQwDQYJKoZIhvcNAQEFBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD +VQQIEwJDQTEVMBMGA1UEChMMT3BlbiB2U3dpdGNoMREwDwYDVQQLEwhzd2l0Y2hj +YTE7MDkGA1UEAxMyT1ZTIHN3aXRjaGNhIENBIENlcnRpZmljYXRlICgyMDE1IEZl +YiAwOSAwNjoyNjowMykwHhcNMTUwMzEwMTcxMTEzWhcNMjUwMzA3MTcxMTEzWjCB +izELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRUwEwYDVQQKEwxPcGVuIHZTd2l0 +Y2gxHzAdBgNVBAsTFk9wZW4gdlN3aXRjaCBjZXJ0aWZpZXIxNzA1BgNVBAMTLmNs +aWVudCBpZDplMWQxNmQ2MS1jMDcwLTQxOWQtOTYxYi00M2Q4YTBkM2IwMzAwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDF31mRQr6wFOgRvzHUtKgx9U/D +6XjDD3XkE2f/RNIDvJj6Wr+TXj4VgcV2oH29KHkLRtR2GsTtAnjnVY/w3JMMhS5m +WJ5QHOidfLSFhUFDxtRIZ8WCj/ghZv+0coiVOJnzoapoFzy512JntwXZt5nC/26f +1kc4a1Cu8x3MHsXObNmtonU1sCyvUZfjDR64GM9tTK73qfMF4papfe90RErV10vn +LyEDaRJSyVyLrTMJ/3+O9xVpIO9ykvDEqrZBh5Lxxo9YtCbKzPIGt0i+yetNxQ8g +b446z/Dcz4o1gqvJIXXaEpRtS6MisE+35En2m2S/bxNhFiqeKBFkXefM2eN7AgMB +AAEwDQYJKoZIhvcNAQEFBQADggEBADmkUbvx9zVtcw1/kSMzr184J3xNBZhmJZ39 +RBXKR1elZ1aVjc4406vsKZiKKWKQA7rC51xzaLk/huKvUez8RU346GugHtUus05x +7MZ2DDiwJNb7hJBBiJ9a0ZyhwCBcQJj95g3686TWYGpc8eEPldACf38SP2etPOmE +QPvIMm3pztNf6JZDFDoV3cwzVEFhQnDHXQE9sR6eLhxpbQqwVJ8tmAr9F5hRr4i7 +SpxHclvZyS+c3nsNb0xluN1Dh8fcy2ITSXfpar+H5B/i1nZA0dBJsW7ChRHAhPQU +QYLXEK2+YnilIr00RMv7qa0vF6vC0lXHjGZhm5GzHW2hXDDUIig= +-----END CERTIFICATE----- +]) +AT_CHECK([ovs-vsctl set Open_vSwitch . ssl=@N1 -- --id=@N1 create SSL private_key=ssl_key.pem certificate=ssl_cert.pem bootstrap_ca_cert=true], [0], [ignore]) +]) + + + +AT_BANNER([ovs-monitor-ipsec (XFRM)]) + +# This test ensures that ovs-monitor-ipsec would properly parse +# IPsec policies from kernel. +AT_SETUP([Parse "ip xfrm policy" output]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_MONITOR_IPSEC_START + +AT_DATA([sbin/ip], [#!/bin/sh +echo 'src 192.168.2.128/32 dst 192.168.2.129/32 proto gre + dir in priority 1794 + mark 1/0x1 + tmpl src 0.0.0.0 dst 0.0.0.0 + proto esp reqid 1 mode transport +src 192.168.2.129/32 dst 192.168.2.128/32 proto gre + dir out priority 1794 + mark 1/0x1 + tmpl src 0.0.0.0 dst 0.0.0.0 + proto esp reqid 1 mode transport' +]) +chmod +x sbin/ip +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec xfrm/policies], [0], [dnl +{'192.168.2.129': [['src 192.168.2.128/32 dst 192.168.2.129/32 proto gre', 'src 192.168.2.128/32 dst 192.168.2.129/32 proto gre']], '192.168.2.128': [['src 192.168.2.129/32 dst 192.168.2.128/32 proto gre', 'src 192.168.2.129/32 dst 192.168.2.128/32 proto gre']]} +]) + +OVS_MONITOR_IPSEC_STOP +AT_CLEANUP + + +# This test ensures that ovs-monitor-ipsec would properly parse +# IPsec security associations from kernel. +AT_SETUP([Parse "ip xfrm state" output]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_MONITOR_IPSEC_START + +AT_DATA([sbin/ip], [#!/bin/sh +echo 'src 192.168.2.129 dst 192.168.2.128 + proto esp spi 0xcdfe5007 reqid 1 mode transport + replay-window 32 + mark 1/0x1 + aead rfc4106(gcm(aes)) 0x3d7cb9ddde30c4e6a870b9c1474b8795a85e564d 96 + sel src 192.168.2.129/32 dst 192.168.2.128/32 +src 192.168.2.128 dst 192.168.2.129 + proto esp spi 0xca623718 reqid 1 mode transport + replay-window 32 + mark 1/0x1 + aead rfc4106(gcm(aes)) 0x7c5b209afe3c93adae68f544f5a1326320b15fe5 96 + sel src 192.168.2.128/32 dst 192.168.2.129/32' +]) +chmod +x sbin/ip +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec xfrm/state], [0], [dnl +{'192.168.2.129': [['sel src 192.168.2.129/32 dst 192.168.2.128/32', 'sel src 192.168.2.128/32 dst 192.168.2.129/32']], '192.168.2.128': [['sel src 192.168.2.129/32 dst 192.168.2.128/32', 'sel src 192.168.2.128/32 dst 192.168.2.129/32']]} +]) +OVS_MONITOR_IPSEC_STOP +AT_CLEANUP + + +AT_BANNER([ovs-monitor-ipsec (strongSwan)]) + +# This test ensures that ovs-monitor-ipsec would ignore non-IPsec tunnels. +AT_SETUP([Ignore non-IPsec tunnels]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_VSWITCHD_START([]) +OVS_MONITOR_IPSEC_START + +AT_CHECK([ovs-vsctl add-port br0 gre0 -- set interface gre0 type=gre options:remote_ip=1.2.3.4]) +# Since ovs-monitor-ipsec does not keep track of non-IPsec tunnels then there +# is no proper way to synchronize this. If ovs-monitor-ipsec did not +# ignore non-IPsec tunnels then this test would occasionally fail. +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +No tunnels configured with IPsec +]) +AT_CHECK([ovs-vsctl del-port br0 gre0]) + +WAIT_FOR_PROPER_IPSEC_CLEANUP +OVS_MONITOR_IPSEC_STOP +OVS_VSWITCHD_STOP +AT_CLEANUP + + +# This test ensures that ovs-monitor-ipsec would properly parse +# output from "ipsec status" command +AT_SETUP([Parse "ipsec status" output]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_MONITOR_IPSEC_START + +AT_DATA([active_tunns], [Routed Connections: + stt0-out-2{3}: ROUTED, TRANSPORT + stt0-out-2{3}: 192.168.2.128/32[[tcp]] === 192.168.2.129/32[[tcp/7471]] + stt0-in-2{2}: ROUTED, TRANSPORT + stt0-in-2{2}: 192.168.2.128/32[[tcp/7471]] === 192.168.2.129/32[[tcp]] +Security Associations (1 up, 0 connecting): + stt0-in-2[[3]]: ESTABLISHED 3 minutes ago, 192.168.2.128[[192.168.2.128]]...192.168.2.129[[192.168.2.129]] + stt0-in-2{2}: INSTALLED, TRANSPORT, ESP SPIs: cbc3e903_i c29b1e5c_o + stt0-in-2{2}: 192.168.2.128/32[[tcp/7471]] === 192.168.2.129/32[[tcp]] + stt0-out-2{3}: INSTALLED, TRANSPORT, ESP SPIs: c958d9fb_i c45ce6b0_o + stt0-out-2{3}: 192.168.2.128/32[[tcp]] === 192.168.2.129/32[[tcp/7471]] +]) + +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec ipsec/status], [0], [dnl +{'stt0': {'stt0-in-2[[3]]': 'stt0-in-2[[3]]: ESTABLISHED 3 minutes ago, 192.168.2.128[[192.168.2.128]]...192.168.2.129[[192.168.2.129]]:', 'stt0-in-2{2}': 'stt0-in-2{2}: 192.168.2.128/32[[tcp/7471]] === 192.168.2.129/32[[tcp]]:', 'stt0-out-2{3}': 'stt0-out-2{3}: 192.168.2.128/32[[tcp]] === 192.168.2.129/32[[tcp/7471]]:'}} +]) + +WAIT_FOR_PROPER_IPSEC_CLEANUP +OVS_MONITOR_IPSEC_STOP +AT_CLEANUP + + +# This test ensures that ovs-monitor-ipsec would properly configure +# ipsec_gre tunnel with Pre-Shared Key +AT_SETUP([ipsec_gre with PSK authentication]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_VSWITCHD_START([]) +OVS_MONITOR_IPSEC_START + +AT_CHECK([ovs-vsctl add-port br0 gre0 -- \ + set interface gre0 type=ipsec_gre \ + options:remote_ip=1.2.3.4 \ + options:psk=swordfish]) +WAIT_FOR_TUNNEL_TO_UPDATE([gre0], [1], [CONFIGURED]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: gre0 v1 (CONFIGURED) + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_gre + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: None + My key: None + His cert: None + PSK: swordfish + Ofport: 1 + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + gre0-1: + +]) +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + +1.2.3.4 : PSK swordfish +]) +AT_CHECK([cat etc/ipsec.conf], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + + +config setup + uniqueids=no + +conn %default + keyingtries=%forever + type=transport + keyexchange=ikev2 + auto=route + mark_in=1/1 + ike=aes128gcm12-aesxcbc-modp1024 + esp=aes128gcm12-modp1024 + +conn gre0-1 + left=0.0.0.0 + right=1.2.3.4 + authby=psk + leftsubnet=%dynamic[[gre]] + rightsubnet=%dynamic[[gre]] + +]) +AT_CHECK([ovs-vsctl del-port gre0]) + +WAIT_FOR_PROPER_IPSEC_CLEANUP +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl +ipsec restart +ipsec update +ipsec update +ipsec stroke down-nb gre0-1 +]) +OVS_MONITOR_IPSEC_STOP +OVS_VSWITCHD_STOP +AT_CLEANUP + + +# This test ensures that ovs-monitor-ipsec would properly configure +# ipsec_stt tunnel with Pre-Shared Key +AT_SETUP([ipsec_stt with PSK authentication]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_VSWITCHD_START([]) +OVS_MONITOR_IPSEC_START + +AT_CHECK([ovs-vsctl add-port br0 stt0 -- \ + set interface stt0 type=ipsec_stt \ + options:remote_ip=1.2.3.4 \ + options:psk=swordfish]) +WAIT_FOR_TUNNEL_TO_UPDATE([stt0], [1], [CONFIGURED]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: stt0 v1 (CONFIGURED) + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_stt + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: None + My key: None + His cert: None + PSK: swordfish + Ofport: 1 + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + stt0-in-1: + stt0-out-1: + +]) +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + +1.2.3.4 : PSK swordfish +]) +AT_CHECK([cat etc/ipsec.conf], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + + +config setup + uniqueids=no + +conn %default + keyingtries=%forever + type=transport + keyexchange=ikev2 + auto=route + mark_in=1/1 + ike=aes128gcm12-aesxcbc-modp1024 + esp=aes128gcm12-modp1024 + +conn stt0-in-1 + left=0.0.0.0 + right=1.2.3.4 + authby=psk + rightsubnet=%dynamic[[tcp/%any]] + leftsubnet=%dynamic[[tcp/7471]] + +conn stt0-out-1 + left=0.0.0.0 + right=1.2.3.4 + authby=psk + rightsubnet=%dynamic[[tcp/7471]] + leftsubnet=%dynamic[[tcp/%any]] + +]) +AT_CHECK([ovs-vsctl del-port stt0]) + +WAIT_FOR_PROPER_IPSEC_CLEANUP +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl +ipsec restart +ipsec update +ipsec update +ipsec stroke down-nb stt0-in-1 +ipsec stroke down-nb stt0-out-1 +]) +OVS_MONITOR_IPSEC_STOP +OVS_VSWITCHD_STOP +AT_CLEANUP + + +# This test ensures that ovs-monitor-ipsec would properly configure +# ipsec_geneve tunnel with Pre-Shared Key +AT_SETUP([ipsec_geneve with PSK authentication]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_VSWITCHD_START([]) +OVS_MONITOR_IPSEC_START + +AT_CHECK([ovs-vsctl add-port br0 geneve0 -- \ + set interface geneve0 type=ipsec_geneve \ + options:remote_ip=1.2.3.4 \ + options:psk=swordfish]) +WAIT_FOR_TUNNEL_TO_UPDATE([geneve0], [1], [CONFIGURED]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: geneve0 v1 (CONFIGURED) + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_geneve + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: None + My key: None + His cert: None + PSK: swordfish + Ofport: 1 + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + geneve0-in-1: + geneve0-out-1: + +]) +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + +1.2.3.4 : PSK swordfish +]) +AT_CHECK([cat etc/ipsec.conf], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + + +config setup + uniqueids=no + +conn %default + keyingtries=%forever + type=transport + keyexchange=ikev2 + auto=route + mark_in=1/1 + ike=aes128gcm12-aesxcbc-modp1024 + esp=aes128gcm12-modp1024 + +conn geneve0-in-1 + left=0.0.0.0 + right=1.2.3.4 + authby=psk + rightsubnet=%dynamic[[udp/%any]] + leftsubnet=%dynamic[[udp/6081]] + +conn geneve0-out-1 + left=0.0.0.0 + right=1.2.3.4 + authby=psk + rightsubnet=%dynamic[[udp/6081]] + leftsubnet=%dynamic[[udp/%any]] + +]) +AT_CHECK([ovs-vsctl del-port geneve0]) + +WAIT_FOR_PROPER_IPSEC_CLEANUP +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl +ipsec restart +ipsec update +ipsec update +ipsec stroke down-nb geneve0-in-1 +ipsec stroke down-nb geneve0-out-1 +]) +OVS_MONITOR_IPSEC_STOP +OVS_VSWITCHD_STOP +AT_CLEANUP + + +# This test ensures that ovs-monitor-ipsec would properly configure +# ipsec_vxlan tunnel with Pre-Shared Key +AT_SETUP([ipsec_vxlan with PSK authentication]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_VSWITCHD_START([]) +OVS_MONITOR_IPSEC_START + +AT_CHECK([ovs-vsctl add-port br0 vxlan0 -- \ + set interface vxlan0 type=ipsec_vxlan \ + options:remote_ip=1.2.3.4 \ + options:psk=swordfish]) +WAIT_FOR_TUNNEL_TO_UPDATE([vxlan0], [1], [CONFIGURED]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: vxlan0 v1 (CONFIGURED) + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_vxlan + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: None + My key: None + His cert: None + PSK: swordfish + Ofport: 1 + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + vxlan0-in-1: + vxlan0-out-1: + +]) +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + +1.2.3.4 : PSK swordfish +]) +AT_CHECK([cat etc/ipsec.conf], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + + +config setup + uniqueids=no + +conn %default + keyingtries=%forever + type=transport + keyexchange=ikev2 + auto=route + mark_in=1/1 + ike=aes128gcm12-aesxcbc-modp1024 + esp=aes128gcm12-modp1024 + +conn vxlan0-in-1 + left=0.0.0.0 + right=1.2.3.4 + authby=psk + rightsubnet=%dynamic[[udp/%any]] + leftsubnet=%dynamic[[udp/4789]] + +conn vxlan0-out-1 + left=0.0.0.0 + right=1.2.3.4 + authby=psk + rightsubnet=%dynamic[[udp/4789]] + leftsubnet=%dynamic[[udp/%any]] + +]) +AT_CHECK([ovs-vsctl del-port vxlan0]) + +WAIT_FOR_PROPER_IPSEC_CLEANUP +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl +ipsec restart +ipsec update +ipsec update +ipsec stroke down-nb vxlan0-in-1 +ipsec stroke down-nb vxlan0-out-1 +]) +OVS_MONITOR_IPSEC_STOP +OVS_VSWITCHD_STOP +AT_CLEANUP + + +# This test ensures that ovs-monitor-ipsec is able to configure IPsec for +# a tunnel that wants to reuse Open vSwitch SSL certificates for +# authentication. Note that previous PSK tests already verified geneve, +# vxlan and stt IPsec tunneling. So there is not much benefit of redoing +# these kind of tests for those tunneling protocols. +AT_SETUP([ipsec_gre with PKI authentication (use_ssl_cert)]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_VSWITCHD_START([]) +OVS_MONITOR_IPSEC_START +INIT_SSL_CERTS + +AT_CHECK([ovs-vsctl add-port br0 gre0 -- \ + set interface gre0 type=ipsec_gre \ + options:remote_ip=1.2.3.4 \ + options:use_ssl_cert=true \ + options:peer_cert="asd"]) +WAIT_FOR_TUNNEL_TO_UPDATE([gre0], [1], [CONFIGURED]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: gre0 v1 (CONFIGURED) + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_gre + Local IP: 0.0.0.0 + Use SSL cert: True + My cert: ssl_cert.pem + My key: ssl_key.pem + His cert: asd + PSK: None + Ofport: 1 + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + gre0-1: + +]) +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + +1.2.3.4 : RSA ssl_key.pem +]) +AT_CHECK([cat etc/ipsec.d/certs/ovs-gre0.pem], [0], [dnl +asd]) +AT_CHECK([cat etc/ipsec.conf], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + + +config setup + uniqueids=no + +conn %default + keyingtries=%forever + type=transport + keyexchange=ikev2 + auto=route + mark_in=1/1 + ike=aes128gcm12-aesxcbc-modp1024 + esp=aes128gcm12-modp1024 + +conn gre0-1 + left=0.0.0.0 + right=1.2.3.4 + rightcert=ovs-1.2.3.4.pem + leftcert=ssl_cert.pem + leftsubnet=%dynamic[[gre]] + rightsubnet=%dynamic[[gre]] + +]) +AT_CHECK([ovs-vsctl del-port gre0]) + +WAIT_FOR_PROPER_IPSEC_CLEANUP +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl +ipsec restart +ipsec update +ipsec update +ipsec stroke down-nb gre0-1 +]) +OVS_MONITOR_IPSEC_STOP +OVS_VSWITCHD_STOP +AT_CLEANUP + + +# This test ensures that ovs-monitor-ipsec would be able to configure +# IPsec tunnel that has it's own Private and Public key (opposed to +# reusing Open vSwitch's SSL credentials) +AT_SETUP([ipsec_gre with PKI authentication]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_VSWITCHD_START([]) +OVS_MONITOR_IPSEC_START + +AT_CHECK([ovs-vsctl add-port br0 gre0 -- \ + set interface gre0 type=ipsec_gre \ + options:remote_ip=1.2.3.4 \ + options:private_key="private_key.pem" \ + options:certificate="public_key.pem" \ + options:peer_cert="asd"]) +WAIT_FOR_TUNNEL_TO_UPDATE([gre0], [1], [CONFIGURED]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: gre0 v1 (CONFIGURED) + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_gre + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: public_key.pem + My key: private_key.pem + His cert: asd + PSK: None + Ofport: 1 + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + gre0-1: + +]) +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + +1.2.3.4 : RSA private_key.pem +]) +AT_CHECK([cat etc/ipsec.conf], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + + +config setup + uniqueids=no + +conn %default + keyingtries=%forever + type=transport + keyexchange=ikev2 + auto=route + mark_in=1/1 + ike=aes128gcm12-aesxcbc-modp1024 + esp=aes128gcm12-modp1024 + +conn gre0-1 + left=0.0.0.0 + right=1.2.3.4 + rightcert=ovs-1.2.3.4.pem + leftcert=public_key.pem + leftsubnet=%dynamic[[gre]] + rightsubnet=%dynamic[[gre]] + +]) +AT_CHECK([ovs-vsctl del-port gre0]) + +WAIT_FOR_PROPER_IPSEC_CLEANUP +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl +ipsec restart +ipsec update +ipsec update +ipsec stroke down-nb gre0-1 +]) +OVS_MONITOR_IPSEC_STOP +OVS_VSWITCHD_STOP +AT_CLEANUP + + +# This test ensures that ovs-monitor-ipsec is able to configure +# two IPsec tunnels without interfering with each other. +AT_SETUP([ipsec_gre and ipsec_stt to two different hosts]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_VSWITCHD_START([]) +OVS_MONITOR_IPSEC_START + + +AT_CHECK([ovs-vsctl add-port br0 gre0 -- \ + set interface gre0 type=ipsec_gre \ + options:remote_ip=1.2.3.4 \ + options:private_key="private_key1.pem" \ + options:certificate="public_key1.pem" \ + options:peer_cert="asd"]) +AT_CHECK([ovs-vsctl add-port br0 stt0 -- \ + set interface stt0 type=ipsec_stt \ + options:remote_ip=1.2.3.5 \ + options:private_key="private_key2.pem" \ + options:certificate="public_key2.pem" \ + options:peer_cert="fgh"]) +WAIT_FOR_TUNNEL_TO_UPDATE([gre0], [1], [CONFIGURED]) +WAIT_FOR_TUNNEL_TO_UPDATE([stt0], [1], [CONFIGURED]) +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + +1.2.3.4 : RSA private_key1.pem +1.2.3.5 : RSA private_key2.pem +]) +AT_CHECK([cat etc/ipsec.conf], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + + +config setup + uniqueids=no + +conn %default + keyingtries=%forever + type=transport + keyexchange=ikev2 + auto=route + mark_in=1/1 + ike=aes128gcm12-aesxcbc-modp1024 + esp=aes128gcm12-modp1024 + +conn gre0-1 + left=0.0.0.0 + right=1.2.3.4 + rightcert=ovs-1.2.3.4.pem + leftcert=public_key1.pem + leftsubnet=%dynamic[[gre]] + rightsubnet=%dynamic[[gre]] + +conn stt0-in-1 + left=0.0.0.0 + right=1.2.3.5 + rightcert=ovs-1.2.3.5.pem + leftcert=public_key2.pem + rightsubnet=%dynamic[[tcp/%any]] + leftsubnet=%dynamic[[tcp/7471]] + +conn stt0-out-1 + left=0.0.0.0 + right=1.2.3.5 + rightcert=ovs-1.2.3.5.pem + leftcert=public_key2.pem + rightsubnet=%dynamic[[tcp/7471]] + leftsubnet=%dynamic[[tcp/%any]] + +]) +AT_CHECK([ovs-vsctl del-port gre0]) +AT_CHECK([ovs-vsctl del-port stt0]) + +WAIT_FOR_PROPER_IPSEC_CLEANUP +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl +ipsec restart +ipsec update +ipsec update +ipsec update +ipsec stroke down-nb gre0-1 +ipsec update +ipsec stroke down-nb stt0-in-1 +ipsec stroke down-nb stt0-out-1 +]) +OVS_MONITOR_IPSEC_STOP +OVS_VSWITCHD_STOP +AT_CLEANUP + + +# This test ensures that ovs-monitor-ipsec is able to configure two +# IPsec tunnels simulatenously to the same remote_ip. +AT_SETUP([ipsec_gre and ipsec_stt to the same host]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_VSWITCHD_START([]) +OVS_MONITOR_IPSEC_START + + +AT_CHECK([ovs-vsctl add-port br0 gre0 -- \ + set interface gre0 type=ipsec_gre \ + options:remote_ip=1.2.3.4 \ + options:private_key="private_key1.pem" \ + options:certificate="public_key1.pem" \ + options:peer_cert="asd"]) +AT_CHECK([ovs-vsctl add-port br0 stt0 -- \ + set interface stt0 type=ipsec_stt \ + options:remote_ip=1.2.3.4 \ + options:private_key="private_key2.pem" \ + options:certificate="public_key2.pem" \ + options:peer_cert="fgh"]) +WAIT_FOR_TUNNEL_TO_UPDATE([stt0], [1], [CONFIGURED]) +WAIT_FOR_TUNNEL_TO_UPDATE([stt0], [1], [CONFIGURED]) +AT_CHECK([cat etc/ipsec.secrets], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + +1.2.3.4 : RSA private_key1.pem +1.2.3.4 : RSA private_key2.pem +]) +AT_CHECK([cat etc/ipsec.conf], [0], [dnl +# Generated by ovs-monitor-ipsec...do not modify by hand! + + +config setup + uniqueids=no + +conn %default + keyingtries=%forever + type=transport + keyexchange=ikev2 + auto=route + mark_in=1/1 + ike=aes128gcm12-aesxcbc-modp1024 + esp=aes128gcm12-modp1024 + +conn gre0-1 + left=0.0.0.0 + right=1.2.3.4 + rightcert=ovs-1.2.3.4.pem + leftcert=public_key1.pem + leftsubnet=%dynamic[[gre]] + rightsubnet=%dynamic[[gre]] + +conn stt0-in-1 + left=0.0.0.0 + right=1.2.3.4 + rightcert=ovs-1.2.3.4.pem + leftcert=public_key2.pem + rightsubnet=%dynamic[[tcp/%any]] + leftsubnet=%dynamic[[tcp/7471]] + +conn stt0-out-1 + left=0.0.0.0 + right=1.2.3.4 + rightcert=ovs-1.2.3.4.pem + leftcert=public_key2.pem + rightsubnet=%dynamic[[tcp/7471]] + leftsubnet=%dynamic[[tcp/%any]] + +]) +AT_CHECK([ovs-vsctl del-port gre0]) +AT_CHECK([ovs-vsctl del-port stt0]) + +WAIT_FOR_PROPER_IPSEC_CLEANUP +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl +ipsec restart +ipsec update +ipsec update +ipsec update +ipsec stroke down-nb gre0-1 +ipsec update +ipsec stroke down-nb stt0-in-1 +ipsec stroke down-nb stt0-out-1 +]) +OVS_MONITOR_IPSEC_STOP +OVS_VSWITCHD_STOP +AT_CLEANUP + + +# This test ensures that ovs-monitor-ipsec can properly update IPsec tunnel's +# configuration by calling "ipsec update" and "ipsec stroke down-nb" +# commands whenver OVSDB configuration changes. +AT_SETUP([Sequence of IPsec tunnel reconfiguration events]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +AT_SKIP_IF([$non_ascii_cwd]) +OVS_VSWITCHD_START([]) +OVS_MONITOR_IPSEC_START + +# Start with tunnel that has 'type' set to 'ipsec_stt' and nothing else +ovs-vsctl add-port br0 tun0 -- set interface tun0 type=ipsec_stt +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [1], [INVALID]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: tun0 v1 (INVALID: 'remote_ip' is not set) + Remote IP: None + Tunnel Type: ipsec_stt + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: None + My key: None + His cert: None + PSK: None + Ofport: Not assigned + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + +]) + +ovs-vsctl set interface tun0 options:remote_ip="1.2.3.4" +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [2], [INVALID]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: tun0 v2 (INVALID: must set either 'psk' or 'peer_cert') + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_stt + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: None + My key: None + His cert: None + PSK: None + Ofport: Not assigned + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + +]) + +ovs-vsctl set interface tun0 options:psk=swordfish +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [3], [CONFIGURED]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: tun0 v3 (CONFIGURED) + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_stt + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: None + My key: None + His cert: None + PSK: swordfish + Ofport: 1 + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + tun0-out-3: + tun0-in-3: + +]) + + +ovs-vsctl set interface tun0 type=ipsec_gre +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [4], [CONFIGURED]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: tun0 v4 (CONFIGURED) + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_gre + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: None + My key: None + His cert: None + PSK: swordfish + Ofport: 1 + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + tun0-4: + +]) + + +ovs-vsctl remove interface tun0 options psk --\ + set Interface tun0 options:peer_cert="asd" +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [5], [INVALID]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: tun0 v5 (INVALID: must set 'certificate' with PKI) + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_gre + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: None + My key: None + His cert: asd + PSK: None + Ofport: Not assigned + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + +]) + + +ovs-vsctl set Interface tun0 options:certificate="my_cert.pem" +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [6], [INVALID]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: tun0 v6 (INVALID: must set 'private_key' with PKI) + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_gre + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: my_cert.pem + My key: None + His cert: asd + PSK: None + Ofport: 2 + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + +]) + + +ovs-vsctl set Interface tun0 options:private_key="my_key.pem" +WAIT_FOR_TUNNEL_TO_UPDATE([tun0], [7], [CONFIGURED]) +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec tunnels/show], [0], [dnl +Interface name: tun0 v7 (CONFIGURED) + Remote IP: 1.2.3.4 + Tunnel Type: ipsec_gre + Local IP: 0.0.0.0 + Use SSL cert: False + My cert: my_cert.pem + My key: my_key.pem + His cert: asd + PSK: None + Ofport: 2 + CFM state: Disabled +Kernel policies installed: +Kernel security associations installed: +Strongswan connections that are active: + tun0-7: + +]) + +AT_CHECK([ovs-vsctl del-port tun0]) +WAIT_FOR_PROPER_IPSEC_CLEANUP +VERIFY_IPSEC_COMMANDS_EXECUTED([dnl +ipsec restart +ipsec update +ipsec update +ipsec update +ipsec update +ipsec stroke down-nb tun0-in-3 +ipsec stroke down-nb tun0-out-3 +ipsec update +ipsec stroke down-nb tun0-4 +ipsec update +ipsec update +ipsec update +ipsec stroke down-nb tun0-7 +]) +# For this test all errors in OVS logs that contain 'netdev' must be ignored +# because ovs-vswitchd also does basic validation +OVS_MONITOR_IPSEC_STOP +OVS_VSWITCHD_STOP(["/netdev/d"]) +AT_CLEANUP diff --git a/tests/testsuite.at b/tests/testsuite.at index 15c385e2c..23b7defbd 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -55,6 +55,7 @@ m4_include([tests/ovs-router.at]) m4_include([tests/lockfile.at]) m4_include([tests/reconnect.at]) m4_include([tests/ovs-vswitchd.at]) +m4_include([tests/ovs-monitor-ipsec.at]) m4_include([tests/ofproto.at]) m4_include([tests/dpif-netdev.at]) m4_include([tests/pmd.at]) diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml index b93da69bd..205578c87 100644 --- a/vswitchd/vswitch.xml +++ b/vswitchd/vswitch.xml @@ -473,8 +473,15 @@ OpenFlow specification mandates the timeout to be at least one second. The default is 10 seconds. </p> - </column> - + </column> + <column name="other_config" key="ipsec_default_mark" + type='{"type": "string"}'> + <p> + Defines the value and mask of SKB mark to use in IPsec shunt + policies. There are three packet forwarding modes in which + IPsec could operate. + </p> + </column> </group> <group title="Status"> @@ -1379,7 +1386,7 @@ <column name="external_ids"/> </group> </table> - + <table name="Port" table="Port or bond configuration."> <p>A port within a <ref table="Bridge"/>.</p> <p>Most commonly, a port has exactly one ``interface,'' pointed to by its @@ -2625,7 +2632,7 @@ <column name="options" key="csum" type='{"type": "boolean"}'> <p> - Optional. Compute encapsulation header (either GRE or UDP) + Optional. Compute encapsulation header (either GRE or UDP) checksums on outgoing packets. Default is disabled, set to <code>true</code> to enable. Checksums present on incoming packets will be validated regardless of this setting. @@ -2643,6 +2650,74 @@ </column> </group> + + <group title="Tunnel Options: IPsec"> + <p> + <code>gre</code>, <code>geneve</code>, and + <code>vxlan</code> interfaces support these options. + </p> + + <column name="options" key="psk" type='{"type": "boolean"}'> + <p> + The preshared secret to negotiate tunnel in PSK mode. + Must be unset when peer_cert is set. + </p> + + <p> + This secret gets populated by ovs-monitor-ipsec daemon + in /etc/ipsec.secrets file. This value must match on both + tunnel ends, because otherwise authentication failure will be + triggered in /var/log/auth.log file by strongSwan. + </p> + + </column> + + <column name="options" key="peer_cert" type='{"type": "boolean"}'> + <p> + The public key of remote peer to negotiate tunnel in PKI mode. + Must be unset when psk is set. + </p> + + <p> + The ovs-monitor-ipsec daemon creates a file with content of this + value under /etc/ipsec.d/certs directory and then references it + from /etc/ipsec.secrets file. This value must be set either to + peer's public certificate contents itself or to the Certificate + Authority of that peer. + </p> + + </column> + + <column name="options" key="private_key" type='{"type": "boolean"}'> + <p> + The private key of this peer to negotiate tunnel in PKI mode. + This file must never ever be leaked, because anyone who posses + it can negotiate tunnel. + Must be unset when psk is set. + </p> + + <p> + ... + </p> + + </column> + + <column name="options" key="certificate" type='{"type": "boolean"}'> + <p> + The public key of this peer to negotiate tunnel in PKI mode. + This file is exhanged over wire during IKE negotiation phase. + Hence it is not a secret. + Must be unset when psk is set. + </p> + + <p> + ... + </p> + + </column> + + </group> + </group> <group title="Tunnel Options: erspan only">