This patch would make strongSwan the default IKE keying daemon for ovs-monitor-ipsec daemon.
It introduces also few new changes to the IPsec configuration that are not backward compatible with the older Racoon daemon: 1. StrongSwan would use IKEv2 protocol to negotiate keys (Opposed to IKEv1 protocol used by Racoon) 2. StrongSwan uses GCM (Galois Counter) mode of operation (Opposed to CBC (Cipher Block Chaining) mode of operation ysed by Racoon). 3. The new ovs-monitor-ipsec daemon supports ipsec_stt, ipsec_geneve and ipsec_vxlan (Opposed to only ipsec_gre supported by Racoon) This patch was tested with strongSwan 5.1.2 that comes with Ubuntu 15.04. Signed-Off-By: Ansis Atteka <aatt...@nicira.com> --- INSTALL.IPsec.md | 97 +++ Makefile.am | 1 + NEWS | 1 + README.md | 3 + debian/control | 8 +- debian/ovs-monitor-ipsec | 966 ++++++++++++++++------------ lib/dpif-netlink.c | 4 +- lib/netdev-vport.c | 29 +- ofproto/ofproto-dpif-ipfix.c | 16 + tests/ovs-monitor-ipsec.at | 1425 +++++++++++++++++++++++++++++++++--------- vswitchd/vswitch.xml | 17 + 11 files changed, 1865 insertions(+), 702 deletions(-) create mode 100644 INSTALL.IPsec.md diff --git a/INSTALL.IPsec.md b/INSTALL.IPsec.md new file mode 100644 index 0000000..e9d2a76 --- /dev/null +++ b/INSTALL.IPsec.md @@ -0,0 +1,97 @@ +How to Encrypt Open vSwitch Tunnels with IPsec +==================================== + +This document describes how to use Open vSwitch with strongSwan 5.1 or +later to provide IPsec security for STT, GENEVE, GRE and VXLAN tunnels. This +document assumes that you followed [INSTALL.md] or installed Open vSwitch +from distribution packaging such as a .deb. + + +Limitations +----------- +- Currently only Debian-based platforms are supported. + +- Backward compatibility with ovs-monitor-ipsec daemon that uses Racoon + is broken. + +- Some older Open vSwitch datapath kernel modules (in Linux Kernel tree) + do not support route lookups with transport protocol ports. In this case + Ethernet over L4 tunneling protocols (e.g. ipsec_stt, ipsec_geneve) would + not work. However, ipsec_gre would still work because it is Ethernet + over L3 tunnelling protocol and does not have concept of transport protocol + ports. + +- Some strongSwan versions might not support certain features. For + example: + - AES GCM ciphers that dramatically increases performance. + - xfrm_acq_expires setting in charon configuration file. + This setting tells strongSwan how aggressively to retry establishing + tunnel if it was rejected by peer due to incomplete or invalid + configuration. + - set_proto_port_transport_sa in charon configuration file that tells + Linux Kernel to filter off unexpected packets that came over IPsec + tunnel. + - IPsec transport mode for NAT deployment. + +Setup +----- + +Install openvswitch-ipsec debian package and strongSwan: + + % apt-get install strongswan + % dpkg -i openvswitch-ipsec_<version>_amd64.deb + + +Create GRE over IPsec tunnel (by using Pre-Shared Key authentication): + + % ovs-vsctl add-port br0 gre0 -- \ + set interface gre0 type=ipsec_gre \ + options:remote_ip=1.2.3.4 \ + options:psk=swordfish]) + +Troubleshooting +--------------- + +Use following ovs-apptcl command to get ovs-monitor-ipsec internal +representation of IPsec tunnels: + + % ovs-appctl -t ovs-monitor-ipsec tunnels/show + + +If there is misconfiguration then ovs-appctl should indicate what problem is +there. For example: + +Interface name: gre0 v5 (CONFIGURED) <---- Should be CONFIGURED. If INVALID + then reason would be provided. + Note that each time OVSDB + configuration changes, version + number is incremented (here it + is v5) + Remote IP: 192.168.2.129 + 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 <--- 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 + in strongSwan + +Bug Reporting +------------- + +Please report problems to b...@openvswitch.org. + +[INSTALL.md]:INSTALL.md diff --git a/Makefile.am b/Makefile.am index 8bc431b..0127d87 100644 --- a/Makefile.am +++ b/Makefile.am @@ -81,6 +81,7 @@ docs = \ INSTALL.Fedora.md \ INSTALL.KVM.md \ INSTALL.Libvirt.md \ + INSTALL.IPsec.md \ INSTALL.NetBSD.md \ INSTALL.RHEL.md \ INSTALL.SSL.md \ diff --git a/NEWS b/NEWS index a480607..caa27d1 100644 --- a/NEWS +++ b/NEWS @@ -81,6 +81,7 @@ Post-v2.3.0 openvswitch.ko but built and loaded automatically as individual kernel modules (vport-*.ko). - Support for STT tunneling. + - Overhauled ovs-monitor-ipsec daemon to use strongSwan instead of Racoon. v2.3.0 - 14 Aug 2014 diff --git a/README.md b/README.md index b590928..dbdeb4a 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,8 @@ To use Open vSwitch... - ...with Libvirt, read [INSTALL.Libvirt.md]. +- ...with IPsec, read [INSTALL.IPsec.md]. + - ...without using a kernel module, read [INSTALL.userspace.md]. For answers to common questions, read [FAQ.md]. @@ -120,6 +122,7 @@ b...@openvswitch.org [INSTALL.Fedora.md]:INSTALL.Fedora.md [INSTALL.KVM.md]:INSTALL.KVM.md [INSTALL.Libvirt.md]:INSTALL.Libvirt.md +[INSTALL.IPsec.md]:INSTALL.IPsec.md [INSTALL.RHEL.md]:INSTALL.RHEL.md [INSTALL.SSL.md]:INSTALL.SSL.md [INSTALL.userspace.md]:INSTALL.userspace.md diff --git a/debian/control b/debian/control index 0fe7018..8a60d7b 100644 --- a/debian/control +++ b/debian/control @@ -82,9 +82,7 @@ Description: Open vSwitch switch implementations Package: openvswitch-ipsec Architecture: linux-any Depends: - ${shlibs:Depends}, ${misc:Depends}, python, - ipsec-tools (>=0.8~alpha20101208), - racoon (>=0.8~alpha20101208), + ${shlibs:Depends}, ${misc:Depends}, python, strongswan (>=5.1), openvswitch-common (= ${binary:Version}), openvswitch-switch (= ${binary:Version}), python-openvswitch (= ${source:Version}) @@ -97,8 +95,8 @@ Description: Open vSwitch GRE-over-IPsec support to support distribution across multiple physical servers similar to VMware's vNetwork distributed vswitch or Cisco's Nexus 1000V. . - The ovs-monitor-ipsec script provides support for encrypting GRE - tunnels with IPsec. + The ovs-monitor-ipsec daemon provides support for encrypting GRE, STT, + GENEVE and VXLAN tunnels with IPsec. Package: openvswitch-pki Architecture: all diff --git a/debian/ovs-monitor-ipsec b/debian/ovs-monitor-ipsec index 414d18b..fa272ca 100755 --- a/debian/ovs-monitor-ipsec +++ b/debian/ovs-monitor-ipsec @@ -1,5 +1,5 @@ #!/usr/bin/python -# Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc. +# Copyright (c) 2009-2015 Nicira, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,34 +18,55 @@ # Uses racoon and setkey to support the configuration. Assumes that # OVS has complete control over IPsec configuration for the box. -# xxx To-do: -# - Doesn't actually check that Interface is connected to bridge -# - If a certificate is badly formed, Racoon will refuse to start. We -# should do a better job of verifying certificates are valid before -# adding an interface to racoon.conf. - import argparse import glob import os +import re import subprocess import sys +from string import Template -import ovs.dirs from ovs.db import error from ovs.db import types -import ovs.util 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") -root_prefix = '' # Prefix for absolute file names, for testing. -SETKEY = "/usr/sbin/setkey" 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 @@ -53,341 +74,540 @@ def unixctl_exit(conn, unused_argv, unused_aux): conn.reply(None) -# Class to configure the racoon daemon, which handles IKE negotiation -class Racoon: - # Default locations for files - conf_file = "/etc/racoon/racoon.conf" - cert_dir = "/etc/racoon/certs" - psk_file = "/etc/racoon/psk.txt" - - # Racoon configuration header we use for IKE - conf_header = """# Configuration file generated by Open vSwitch -# -# Do not modify by hand! - -path pre_shared_key "%s"; -path certificate "%s"; - -""" - - # Racoon configuration footer we use for IKE - conf_footer = """sainfo anonymous { - pfs_group 2; - lifetime time 1 hour; - encryption_algorithm aes; - authentication_algorithm hmac_sha1, hmac_md5; - compression_algorithm deflate; -} - -""" - - # Certificate entry template. - cert_entry = """remote %s { - exchange_mode main; - nat_traversal on; - ike_frag on; - certificate_type x509 "%s" "%s"; - my_identifier asn1dn; - peers_identifier asn1dn; - peers_certfile x509 "%s"; - verify_identifier on; - proposal { - encryption_algorithm aes; - hash_algorithm sha1; - authentication_method rsasig; - dh_group 2; - } -} - -""" - - # Pre-shared key template. - psk_entry = """remote %s { - exchange_mode main; - nat_traversal on; - proposal { - encryption_algorithm aes; - hash_algorithm sha1; - authentication_method pre_shared_key; - dh_group 2; - } -} - -""" - - def __init__(self): - self.psk_hosts = {} - self.cert_hosts = {} - - if not os.path.isdir(root_prefix + self.cert_dir): - os.mkdir(self.cert_dir) - - # Clean out stale peer certs from previous runs - for ovs_cert in glob.glob("%s%s/ovs-*.pem" - % (root_prefix, self.cert_dir)): +class XFRM(object): + """This class is a simple wrapper around ip-xfrm (8) command line + utility. For now we are using this class only so that ovs-monitor-ipsec + could verify that IKE keying daemon has installed IPsec policies and + security associations into kernel.""" + + 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 = {"ipsec_gre" : Template("""\ +conn $ifname-$version +$auth_section + leftsubnet=%dynamic[gre] + rightsubnet=%dynamic[gre] + +"""), "ipsec_gre64" : Template("""\ +conn $ifname-$version +$auth_section + leftsubnet=%dynamic[gre] + rightsubnet=%dynamic[gre] + +"""), "ipsec_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] + +"""), "ipsec_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] + +"""), "ipsec_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 + 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' should not change because it is key + self.version = 0 # this is tunnel OVSDB configuration version + 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 taking 'row' + from OVSDB interface table as source. If configuration changed in + OVSDB then this function returns True. Otherwise, it returls + 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"), + "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 for this IPsec tunnel so that we could tell + # apart old and new tunnles 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): + 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 withhold this + # tunnel from having any configuration on 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" + else: + self.invalid_reason = ": must set either 'psk' or 'peer_cert'" + if self.invalid_reason: + return False + return True + + def _cleanup_old_state(self): + if self.cert_file: try: - os.remove(ovs_cert) + os.remove(self.cert_file) except OSError: - vlog.warn("couldn't remove %s" % ovs_cert) - - # Replace racoon's conf file with our template - self.commit() - - def reload(self): - exitcode = subprocess.call([root_prefix + "/etc/init.d/racoon", - "reload"]) - if exitcode != 0: - # Racoon is finicky about its configuration file and will - # refuse to start if it sees something it doesn't like - # (e.g., a certificate file doesn't exist). Try restarting - # the process before giving up. - vlog.warn("attempting to restart racoon") - exitcode = subprocess.call([root_prefix + "/etc/init.d/racoon", - "restart"]) - if exitcode != 0: - vlog.warn("couldn't reload racoon") - - def commit(self): - # Rewrite the Racoon configuration file - conf_file = open(root_prefix + self.conf_file, 'w') - conf_file.write(Racoon.conf_header % (self.psk_file, self.cert_dir)) - - for host, vals in self.cert_hosts.iteritems(): - conf_file.write(Racoon.cert_entry % (host, vals["certificate"], - vals["private_key"], vals["peer_cert_file"])) - - for host in self.psk_hosts: - conf_file.write(Racoon.psk_entry % host) - - conf_file.write(Racoon.conf_footer) - conf_file.close() - - # Rewrite the pre-shared keys file; it must only be readable by root. - orig_umask = os.umask(0077) - psk_file = open(root_prefix + Racoon.psk_file, 'w') - os.umask(orig_umask) - - psk_file.write("# Generated by Open vSwitch...do not modify by hand!") - psk_file.write("\n\n") - for host, vals in self.psk_hosts.iteritems(): - psk_file.write("%s %s\n" % (host, vals["psk"])) - psk_file.close() - - self.reload() - - def _add_psk(self, host, psk): - if host in self.cert_hosts: - raise error.Error("host %s already defined for cert" % host) - - self.psk_hosts[host] = psk - self.commit() - - def _verify_certs(self, vals): - # Racoon will refuse to start if the certificate files don't - # exist, so verify that they're there. - if not os.path.isfile(root_prefix + vals["certificate"]): - raise error.Error("'certificate' file does not exist: %s" - % vals["certificate"]) - elif not os.path.isfile(root_prefix + vals["private_key"]): - raise error.Error("'private_key' file does not exist: %s" - % vals["private_key"]) - - # Racoon won't start if a given certificate or private key isn't - # valid. This is a weak test, but will detect the most flagrant - # errors. - if vals["peer_cert"].find("-----BEGIN CERTIFICATE-----") == -1: - raise error.Error("'peer_cert' is not in valid PEM format") - - cert = open(root_prefix + vals["certificate"]).read() - if cert.find("-----BEGIN CERTIFICATE-----") == -1: - raise error.Error("'certificate' is not in valid PEM format") - - cert = open(root_prefix + vals["private_key"]).read() - if cert.find("-----BEGIN RSA PRIVATE KEY-----") == -1: - raise error.Error("'private_key' is not in valid PEM format") - - def _add_cert(self, host, vals): - if host in self.psk_hosts: - raise error.Error("host %s already defined for psk" % host) - - if vals["certificate"] == None: - raise error.Error("'certificate' not defined for %s" % host) - elif vals["private_key"] == None: - # Assume the private key is stored in the same PEM file as - # the certificate. We make a copy of "vals" so that we don't - # modify the original "vals", which would cause the script - # to constantly think that the configuration has changed - # in the database. - vals = vals.copy() - vals["private_key"] = vals["certificate"] - - self._verify_certs(vals) - - # The peer's certificate comes to us in PEM format as a string. - # Write that string to a file for Racoon to use. - f = open(root_prefix + vals["peer_cert_file"], "w") - f.write(vals["peer_cert"]) - f.close() + 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): + + STRONGSWAN_CONF = """%s +charon.plugins.kernel-netlink.set_proto_port_transport_sa = yes +charon_conf.write("charon.plugins.kernel-netlink.xfrm_ack_expires = 10 +""" % (FILE_HEADER) + IPSEC_MARK = "1/1" + CONF_HEADER = """%s +config setup + uniqueids=no + +conn %%default + keyingtries=%%forever + type=transport + keyexchange=ikev2 + auto=route + mark_in=%s + ike=aes128gcm12-aesxcbc-modp1024 + esp=aes128gcm12-modp1024 + +""" % (FILE_HEADER, IPSEC_MARK) + + + def __init__(self, strongswan_root_prefix): + self.private_key = None + self.public_cert = 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 restart(self): + self._initial_configuration() + vlog.info("restarting strongSwan") + subprocess.call([self.IPSEC, "restart"]) + + def is_tunneling_type_supported(self, tunnel_type): + return tunnel_type in StrongSwanTunnel.transp_tmpl + + def add_tunnel(self, name, row): + vlog.info("Tunnel %s appeared in OVSDB" % (name)) + self.tunnels[name] = StrongSwanTunnel(name, row) + + def update_tunnel(self, name, row): + t = self.tunnels[name] + if t.update_conf(row): + vlog.info("Tunnel's '%s' configuration changed in OVSDB to %u" % + (t.name, t.version)) + + def del_tunnel(self, name): + vlog.info("Tunnel %s disappeared from OVSDB" % (name)) + self.tunnels[name].mark_for_removal() + + def update_ssl_credentials(self, credentials): + self.public_cert = credentials[0] + self.private_key = credentials[1] + + 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 overall + strongSwan configuration (i.e. individual tunnel states). + 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) + 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. + """ + 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 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" comman). This means that we should not be concerned + # about possibility of ovs-monitor-ipsec to block for each tunnel + # while strongSwan sends IKE messages. + 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]) + - self.cert_hosts[host] = vals - self.commit() - - def _del_cert(self, host): - peer_cert_file = self.cert_hosts[host]["peer_cert_file"] - del self.cert_hosts[host] - self.commit() - try: - os.remove(root_prefix + peer_cert_file) - except OSError: - pass - - def add_entry(self, host, vals): - if vals["peer_cert"]: - self._add_cert(host, vals) - elif vals["psk"]: - self._add_psk(host, vals) - - def del_entry(self, host): - if host in self.cert_hosts: - self._del_cert(host) - elif host in self.psk_hosts: - del self.psk_hosts[host] - self.commit() - - -# Class to configure IPsec on a system using racoon for IKE and setkey -# for maintaining the Security Association Database (SAD) and Security -# Policy Database (SPD). Only policies for GRE are supported. -class IPsec: - def __init__(self): - self.sad_flush() - self.spd_flush() - self.racoon = Racoon() - self.entries = [] - - def call_setkey(self, cmds): - try: - p = subprocess.Popen([root_prefix + SETKEY, "-c"], - stdin=subprocess.PIPE, + 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) - except: - vlog.err("could not call %s%s" % (root_prefix, SETKEY)) - sys.exit(1) - - # xxx It is safer to pass the string into the communicate() - # xxx method, but it didn't work for slightly longer commands. - # xxx An alternative may need to be found. - p.stdin.write(cmds) - return p.communicate()[0] - - def get_spi(self, local_ip, remote_ip, proto="esp"): - # Run the setkey dump command to retrieve the SAD. Then, parse - # the output looking for SPI buried in the output. Note that - # multiple SAD entries can exist for the same "flow", since an - # older entry could be in a "dying" state. - spi_list = [] - host_line = "%s %s" % (local_ip, remote_ip) - results = self.call_setkey("dump ;\n").split("\n") - for i in range(len(results)): - if results[i].strip() == host_line: - # The SPI is in the line following the host pair - spi_line = results[i + 1] - if (spi_line[1:4] == proto): - spi = spi_line.split()[2] - spi_list.append(spi.split('(')[1].rstrip(')')) - return spi_list - - def sad_flush(self): - self.call_setkey("flush;\n") - - def sad_del(self, local_ip, remote_ip): - # To delete all SAD entries, we should be able to use setkey's - # "deleteall" command. Unfortunately, it's fundamentally broken - # on Linux and not documented as such. - cmds = "" - - # Delete local_ip->remote_ip SAD entries - spi_list = self.get_spi(local_ip, remote_ip) - for spi in spi_list: - cmds += "delete %s %s esp %s;\n" % (local_ip, remote_ip, spi) - - # Delete remote_ip->local_ip SAD entries - spi_list = self.get_spi(remote_ip, local_ip) - for spi in spi_list: - cmds += "delete %s %s esp %s;\n" % (remote_ip, local_ip, spi) - - if cmds: - self.call_setkey(cmds) - - def spd_flush(self): - self.call_setkey("spdflush;\n") - - def spd_add(self, local_ip, remote_ip): - cmds = ("spdadd %s %s gre -P out ipsec esp/transport//require;\n" % - (local_ip, remote_ip)) - cmds += ("spdadd %s %s gre -P in ipsec esp/transport//require;\n" % - (remote_ip, local_ip)) - self.call_setkey(cmds) - - def spd_del(self, local_ip, remote_ip): - cmds = "spddelete %s %s gre -P out;\n" % (local_ip, remote_ip) - cmds += "spddelete %s %s gre -P in;\n" % (remote_ip, local_ip) - self.call_setkey(cmds) - - def add_entry(self, local_ip, remote_ip, vals): - if remote_ip in self.entries: - raise error.Error("host %s already configured for ipsec" - % remote_ip) - - self.racoon.add_entry(remote_ip, vals) - self.spd_add(local_ip, remote_ip) - - self.entries.append(remote_ip) - - def del_entry(self, local_ip, remote_ip): - if remote_ip in self.entries: - self.racoon.del_entry(remote_ip) - self.spd_del(local_ip, remote_ip) - self.sad_del(local_ip, remote_ip) - - self.entries.remove(remote_ip) - - -def update_ipsec(ipsec, interfaces, new_interfaces): - for name, vals in interfaces.iteritems(): - if name not in new_interfaces: - ipsec.del_entry(vals["local_ip"], vals["remote_ip"]) - - for name, vals in new_interfaces.iteritems(): - orig_vals = interfaces.get(name) - if orig_vals: - # Configuration for this host already exists. Check if it's - # changed. We use set difference, since we want to ignore - # any local additions to "orig_vals" that we've made - # (e.g. the "peer_cert_file" key). - if set(vals.items()) - set(orig_vals.items()): - ipsec.del_entry(vals["local_ip"], vals["remote_ip"]) - else: + 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 - try: - ipsec.add_entry(vals["local_ip"], vals["remote_ip"], vals) - except error.Error, msg: - vlog.warn("skipping ipsec config for %s: %s" % (name, msg)) + if not ifname[0] in conns: + conns[ifname[0]] = {} + (conns[ifname[0]])[tunnel_name[0]] = line + return conns -def get_ssl_cert(data): + 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_ssl_table(data): + ssl = (None, None) for ovs_rec in data["Open_vSwitch"].rows.itervalues(): if ovs_rec.ssl: - ssl = ovs_rec.ssl[0] - if ssl.certificate and ssl.private_key: - return (ssl.certificate, ssl.private_key) + ssl = (ovs_rec.ssl[0].certificate, ovs_rec.ssl[0].private_key) + break + keyer.update_ssl_credentials(ssl) - return None +def read_ovsdb_interface_table(data): + ifaces = set() + for row in data["Interface"].rows.itervalues(): + if not keyer.is_tunneling_type_supported(row.type): + continue + if row.name in keyer.tunnels: + keyer.update_tunnel(row.name, row) + else: + keyer.add_tunnel(row.name, row) + ifaces.add(row.name) + for tunnel in keyer.tunnels.keys(): + if not tunnel in ifaces: + keyer.del_tunnel(tunnel) + +def read_ovsdb(data): + """This function reads all OVSDB configuration that ovs-monitor-ipsec + is interested in.""" + read_ovsdb_ssl_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.") @@ -401,83 +621,55 @@ def main(): ovs.vlog.handle_args(args) ovs.daemon.handle_args(args) - global root_prefix - if args.root_prefix: - root_prefix = args.root_prefix + global keyer + global xfrm + + xfrm = XFRM(args.root_prefix if args.root_prefix else "") + keyer = StrongSwanKeyer(args.root_prefix if args.root_prefix else "") remote = args.database schema_helper = ovs.db.idl.SchemaHelper() - schema_helper.register_columns("Interface", ["name", "type", "options"]) + schema_helper.register_columns("Interface", + ["name", "type", "options", "cfm_fault", + "ofport"]) schema_helper.register_columns("Open_vSwitch", ["ssl"]) 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) - ipsec = IPsec() - - interfaces = {} - seqno = idl.change_seqno # Sequence number when we last processed the db + seqno = idl.change_seqno # Sequence number when we last processed OVSDB + keyer.restart() while True: unixctl_server.run() if exiting: break idl.run() - if seqno == idl.change_seqno: - poller = ovs.poller.Poller() - unixctl_server.wait(poller) - idl.wait(poller) - poller.block() - continue - seqno = idl.change_seqno - - ssl_cert = get_ssl_cert(idl.tables) - - new_interfaces = {} - for rec in idl.tables["Interface"].rows.itervalues(): - if rec.type == "ipsec_gre" or rec.type == "ipsec_gre64": - name = rec.name - options = rec.options - peer_cert_name = "ovs-%s.pem" % (options.get("remote_ip")) - entry = { - "remote_ip": options.get("remote_ip"), - "local_ip": options.get("local_ip", "0.0.0.0/0"), - "certificate": options.get("certificate"), - "private_key": options.get("private_key"), - "use_ssl_cert": options.get("use_ssl_cert"), - "peer_cert": options.get("peer_cert"), - "peer_cert_file": Racoon.cert_dir + "/" + peer_cert_name, - "psk": options.get("psk")} - - if entry["peer_cert"] and entry["psk"]: - vlog.warn("both 'peer_cert' and 'psk' defined for %s" - % name) - continue - elif not entry["peer_cert"] and not entry["psk"]: - vlog.warn("no 'peer_cert' or 'psk' defined for %s" % name) - continue - - # The "use_ssl_cert" option is deprecated and will - # likely go away in the near future. - if entry["use_ssl_cert"] == "true": - if not ssl_cert: - vlog.warn("no valid SSL entry for %s" % name) - continue - - entry["certificate"] = ssl_cert[0] - entry["private_key"] = ssl_cert[1] + if seqno != idl.change_seqno: + read_ovsdb(idl.tables) + seqno = idl.change_seqno - new_interfaces[name] = entry + keyer.run() - if interfaces != new_interfaces: - update_ipsec(ipsec, interfaces, new_interfaces) - interfaces = new_interfaces + poller = ovs.poller.Poller() + unixctl_server.wait(poller) + idl.wait(poller) + poller.block() unixctl_server.close() idl.close() diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c index 6838def..c3c50c9 100644 --- a/lib/dpif-netlink.c +++ b/lib/dpif-netlink.c @@ -792,13 +792,13 @@ netdev_to_ovs_vport_type(const struct netdev *netdev) return OVS_VPORT_TYPE_INTERNAL; } else if (strstr(type, "stt")) { return OVS_VPORT_TYPE_STT; - } else if (!strcmp(type, "geneve")) { + } else if (strstr(type, "geneve")) { return OVS_VPORT_TYPE_GENEVE; } else if (strstr(type, "gre64")) { return OVS_VPORT_TYPE_GRE64; } else if (strstr(type, "gre")) { return OVS_VPORT_TYPE_GRE; - } else if (!strcmp(type, "vxlan")) { + } else if (strstr(type, "vxlan")) { return OVS_VPORT_TYPE_VXLAN; } else if (!strcmp(type, "lisp")) { return OVS_VPORT_TYPE_LISP; diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c index ea9abf9..aaeddca 100644 --- a/lib/netdev-vport.c +++ b/lib/netdev-vport.c @@ -158,8 +158,8 @@ netdev_vport_needs_dst_port(const struct netdev *dev) const char *type = netdev_get_type(dev); return (class->get_config == get_tunnel_config && - (!strcmp("geneve", type) || !strcmp("vxlan", type) || - !strcmp("lisp", type) || !strcmp("stt", type)) ); + (strstr("geneve", type) || strstr("vxlan", type) || + !strcmp("lisp", type) || strstr("stt", type)) ); } const char * @@ -252,13 +252,13 @@ netdev_vport_construct(struct netdev *netdev_) eth_addr_random(dev->etheraddr); /* Add a default destination port for tunnel ports if none specified. */ - if (!strcmp(type, "geneve")) { + if (strstr(type, "geneve")) { dev->tnl_cfg.dst_port = htons(GENEVE_DST_PORT); - } else if (!strcmp(type, "vxlan")) { + } else if (strstr(type, "vxlan")) { dev->tnl_cfg.dst_port = htons(VXLAN_DST_PORT); } else if (!strcmp(type, "lisp")) { dev->tnl_cfg.dst_port = htons(LISP_DST_PORT); - } else if (!strcmp(type, "stt")) { + } else if (strstr(type, "stt")) { dev->tnl_cfg.dst_port = htons(STT_DST_PORT); } @@ -442,11 +442,11 @@ set_tunnel_config(struct netdev *dev_, const struct smap *args) memset(&tnl_cfg, 0, sizeof tnl_cfg); /* Add a default destination port for tunnel ports if none specified. */ - if (!strcmp(type, "geneve")) { + if (strstr(type, "geneve")) { tnl_cfg.dst_port = htons(GENEVE_DST_PORT); } - if (!strcmp(type, "vxlan")) { + if (strstr(type, "vxlan")) { tnl_cfg.dst_port = htons(VXLAN_DST_PORT); } @@ -454,7 +454,7 @@ set_tunnel_config(struct netdev *dev_, const struct smap *args) tnl_cfg.dst_port = htons(LISP_DST_PORT); } - if (!strcmp(type, "stt")) { + if (strstr(type, "stt")) { tnl_cfg.dst_port = htons(STT_DST_PORT); } @@ -695,10 +695,10 @@ get_tunnel_config(const struct netdev *dev, struct smap *args) uint16_t dst_port = ntohs(tnl_cfg.dst_port); const char *type = netdev_get_type(dev); - if ((!strcmp("geneve", type) && dst_port != GENEVE_DST_PORT) || - (!strcmp("vxlan", type) && dst_port != VXLAN_DST_PORT) || + if ((strstr("geneve", type) && dst_port != GENEVE_DST_PORT) || + (strstr("vxlan", type) && dst_port != VXLAN_DST_PORT) || (!strcmp("lisp", type) && dst_port != LISP_DST_PORT) || - (!strcmp("stt", type) && dst_port != STT_DST_PORT)) { + (strstr("stt", type) && dst_port != STT_DST_PORT)) { smap_add_format(args, "dst_port", "%d", dst_port); } } @@ -1402,6 +1402,9 @@ netdev_vport_tunnel_register(void) TUNNEL_CLASS("geneve", "genev_sys", netdev_geneve_build_header, push_udp_header, netdev_geneve_pop_header), + TUNNEL_CLASS("ipsec_geneve", "genev_sys", netdev_geneve_build_header, + push_udp_header, + netdev_geneve_pop_header), TUNNEL_CLASS("gre", "gre_sys", netdev_gre_build_header, netdev_gre_push_header, netdev_gre_pop_header), @@ -1411,8 +1414,12 @@ netdev_vport_tunnel_register(void) TUNNEL_CLASS("vxlan", "vxlan_sys", netdev_vxlan_build_header, push_udp_header, netdev_vxlan_pop_header), + TUNNEL_CLASS("ipsec_vxlan", "vxlan_sys", netdev_vxlan_build_header, + push_udp_header, + netdev_vxlan_pop_header), TUNNEL_CLASS("lisp", "lisp_sys", NULL, NULL, NULL), TUNNEL_CLASS("stt", "stt_sys", NULL, NULL, NULL), + TUNNEL_CLASS("ipsec_stt", "stt_sys", NULL, NULL, NULL), }; static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c index 8a931d6..dda6505 100644 --- a/ofproto/ofproto-dpif-ipfix.c +++ b/ofproto/ofproto-dpif-ipfix.c @@ -67,6 +67,9 @@ enum dpif_ipfix_tunnel_type { DPIF_IPFIX_TUNNEL_STT = 0x04, DPIF_IPFIX_TUNNEL_IPSEC_GRE = 0x05, DPIF_IPFIX_TUNNEL_GENEVE = 0x07, + DPIF_IPFIX_TUNNEL_IPSEC_STT = 0x08, + DPIF_IPFIX_TUNNEL_IPSEC_VXLAN = 0x09, + DPIF_IPFIX_TUNNEL_IPSEC_GENEVE = 0x0a, NUM_DPIF_IPFIX_TUNNEL }; @@ -304,6 +307,10 @@ static uint8_t tunnel_protocol[NUM_DPIF_IPFIX_TUNNEL] = { IPPROTO_GRE, /* DPIF_IPFIX_TUNNEL_IPSEC_GRE */ 0 , /* reserved */ IPPROTO_UDP, /* DPIF_IPFIX_TUNNEL_GENEVE*/ + IPPROTO_TCP, /* DPIF_IPFIX_TUNNEL_IPSEC_STT = 0x08 */ + IPPROTO_UDP, /* DPIF_IPFIX_TUNNEL_IPSEC_VXLAN = 0x09 */ + IPPROTO_UDP, /* DPIF_IPFIX_TUNNEL_IPSEC_GENEVE = 0x0a */ + }; OVS_PACKED( @@ -603,15 +610,24 @@ dpif_ipfix_add_tunnel_port(struct dpif_ipfix *di, struct ofport *ofport, } else if (strcmp(type, "vxlan") == 0) { dip->tunnel_type = DPIF_IPFIX_TUNNEL_VXLAN; dip->tunnel_key_length = 3; + } else if (strcmp(type, "ipsec_vxlan") == 0) { + dip->tunnel_type = DPIF_IPFIX_TUNNEL_IPSEC_VXLAN; + dip->tunnel_key_length = 3; } else if (strcmp(type, "lisp") == 0) { dip->tunnel_type = DPIF_IPFIX_TUNNEL_LISP; dip->tunnel_key_length = 3; } else if (strcmp(type, "geneve") == 0) { dip->tunnel_type = DPIF_IPFIX_TUNNEL_GENEVE; dip->tunnel_key_length = 3; + } else if (strcmp(type, "ipsec_geneve") == 0) { + dip->tunnel_type = DPIF_IPFIX_TUNNEL_IPSEC_GENEVE; + dip->tunnel_key_length = 3; } else if (strcmp(type, "stt") == 0) { dip->tunnel_type = DPIF_IPFIX_TUNNEL_STT; dip->tunnel_key_length = 8; + } else if (strcmp(type, "ipsec_stt") == 0) { + dip->tunnel_type = DPIF_IPFIX_TUNNEL_IPSEC_STT; + dip->tunnel_key_length = 8; } else { free(dip); goto out; diff --git a/tests/ovs-monitor-ipsec.at b/tests/ovs-monitor-ipsec.at index 67705fa..eade24d 100644 --- a/tests/ovs-monitor-ipsec.at +++ b/tests/ovs-monitor-ipsec.at @@ -1,316 +1,1147 @@ -AT_BANNER([ovs-monitor-ipsec]) +# CREATE_FAKE_IPSEC_UTILITY([]) +# +# This macro creates fake "ipsec" utility that mimics strongswan's "ipsec" +# command. It simply keeps track of what tunnels were created with +# "ipsec update" command from "ipsec.conf" file and then removed with +# "ipsec stroke down-nb" command. +m4_define([CREATE_FAKE_IPSEC_UTILITY], [ + mkdir sbin usr usr/sbin etc etc/ipsec.d etc/ipsec.d/certs etc/strongswan.d + AT_DATA([active_tunns], [dnl +]) + AT_DATA([usr/sbin/ipsec], [#!/bin/sh +echo 'ipsec' @S|@@ >> $OVS_SYSCONFDIR/actions -AT_SETUP([ovs-monitor-ipsec]) -AT_SKIP_IF([test $HAVE_PYTHON = no]) -AT_SKIP_IF([$non_ascii_cwd]) +case "@S|@1" in + stroke) + case "@S|@2" in + down-nb) + sed -i "/@S|@3/d" $OVS_SYSCONFDIR/active_tunns + ;; + esac + ;; + update) + grep conn @S|@OVS_SYSCONFDIR/etc/ipsec.conf | grep -v "conn %default" | sed 's/^conn\s//g'> @S|@OVS_SYSCONFDIR/new_tunns + mv @S|@OVS_SYSCONFDIR/active_tunns @S|@OVS_SYSCONFDIR/old_tunns + sort @S|@OVS_SYSCONFDIR/old_tunns @S|@OVS_SYSCONFDIR/new_tunns | uniq > @S|@OVS_SYSCONFDIR/active_tunns + ;; + status) + cat @S|@OVS_SYSCONFDIR/active_tunns | sed "s/$/:/g" + ;; +esac -OVS_RUNDIR=`pwd`; export OVS_RUNDIR -OVS_DBDIR=`pwd`; export OVS_DBDIR -OVS_PKGDATADIR=`pwd`; export OVS_PKGDATADIR -cp "$top_srcdir/vswitchd/vswitch.ovsschema" . +exit 0 +]) + chmod +x usr/sbin/ipsec +]) -ON_EXIT([kill `cat pid ovs-monitor-ipsec.pid`]) -mkdir etc etc/init.d etc/racoon etc/racoon/certs -mkdir usr usr/sbin +# CREATE_FAKE_IP_UTILITY([]) +# +# This macro creates fake "ip" utility that mimics "ip xfrm" commands's +# behavior so that we could ensure that ovs-monitor-ipsec knows how to +# parse policies and security associations dumped from Linux Kernel. +m4_define([CREATE_FAKE_IP_UTILITY], [ + AT_DATA([sbin/ip], [#!/bin/sh +]) + chmod +x sbin/ip +]) -AT_DATA([etc/init.d/racoon], [dnl -#! /bin/sh -echo "racoon: $@" >&3 -exit 0 + +# OVS_MONITOR_IPSEC_START([]) +# +# This macro starts ovs-monitor-ipsec, ovs-vswitchd and ovsdb-server +# daemons. +m4_define([OVS_MONITOR_IPSEC_START], [ + OVS_RUNDIR=`pwd`; export OVS_RUNDIR + OVS_DBDIR=`pwd`; export OVS_DBDIR + OVS_PKGDATADIR=`pwd`; export OVS_PKGDATADIR + cp "$top_srcdir/vswitchd/vswitch.ovsschema" . + + dnl Create fake "ipsec" utility that would try to emulate strongSwan's + dnl behavior. We have to do this, because tests should not interfere + dnl with the real strongSwan instance. + CREATE_FAKE_IPSEC_UTILITY + + dnl Create fake "ip" utility that would print IPsec policies and + dnl security associations just as kernel would. + CREATE_FAKE_IP_UTILITY + + pwd=`pwd` + OVS_VSWITCHD_START + AT_CHECK([$PYTHON $top_srcdir/debian/ovs-monitor-ipsec --detach --pidfile=$OVS_RUNDIR/ovs-monitor-ipsec.pid --log-file "--root-prefix=`pwd`" unix:$OVS_RUNDIR/db.sock], + [0], [], [stderr]) + AT_CAPTURE_FILE([ovs-monitor-ipsec.log]) + AT_CHECK([[sed < stderr ' +/vlog|INFO|opened log file/d']]) + trap 'kill `cat ovs-vswitchd.pid ovsdb-server.pid ovs-monitor-ipsec.pid`' 0 ]) -chmod +x etc/init.d/racoon - -AT_DATA([usr/sbin/setkey], [dnl -#! /bin/sh -exec >&3 -echo "setkey:" -while read line; do - echo "> $line" -done -]) -chmod +x usr/sbin/setkey - -touch etc/racoon/certs/ovs-stale.pem - -ovs_vsctl () { - ovs-vsctl --no-wait -vreconnect:emer --db=unix:socket "$@" -} -trim () { # Removes blank lines and lines starting with # from input. - sed -e '/^#/d' -e '/^[ ]*$/d' "$@" -} - -### -### Start ovsdb-server. -### -OVS_VSCTL_SETUP - -### -### Start ovs-monitor-ipsec and wait for it to delete the stale cert. -### -AT_CHECK( - [$PYTHON $top_srcdir/debian/ovs-monitor-ipsec "--root-prefix=`pwd`" \ - "--pidfile=`pwd`/ovs-monitor-ipsec.pid" \ - unix:socket 2>log 3>actions &]) -AT_CAPTURE_FILE([log]) -AT_CAPTURE_FILE([actions]) -OVS_WAIT_UNTIL([test ! -f etc/racoon/certs/ovs-stale.pem]) - -### -### Add an ipsec_gre psk interface and check what ovs-monitor-ipsec does -### -AT_CHECK([ovs_vsctl \ - -- add-br br0 \ - -- add-port br0 gre0 \ - -- set interface gre0 type=ipsec_gre \ - options:remote_ip=1.2.3.4 \ - options:psk=swordfish]) -OVS_WAIT_UNTIL([test -f actions && grep 'spdadd 1.2.3.4' actions >/dev/null]) -AT_CHECK([cat actions], [0], [dnl -setkey: -> flush; -setkey: -> spdflush; -racoon: reload -racoon: reload -setkey: -> spdadd 0.0.0.0/0 1.2.3.4 gre -P out ipsec esp/transport//require; -> spdadd 1.2.3.4 0.0.0.0/0 gre -P in ipsec esp/transport//require; -]) -AT_CHECK([trim etc/racoon/psk.txt], [0], [1.2.3.4 swordfish -]) -AT_CHECK([trim etc/racoon/racoon.conf], [0], [dnl -path pre_shared_key "/etc/racoon/psk.txt"; -path certificate "/etc/racoon/certs"; -remote 1.2.3.4 { - exchange_mode main; - nat_traversal on; - proposal { - encryption_algorithm aes; - hash_algorithm sha1; - authentication_method pre_shared_key; - dh_group 2; - } -} -sainfo anonymous { - pfs_group 2; - lifetime time 1 hour; - encryption_algorithm aes; - authentication_algorithm hmac_sha1, hmac_md5; - compression_algorithm deflate; -} -]) - -### -### Delete the ipsec_gre interface and check what ovs-monitor-ipsec does -### -AT_CHECK([ovs_vsctl del-port gre0]) -OVS_WAIT_UNTIL([test `wc -l < actions` -ge 17]) -AT_CHECK([sed '1,9d' actions], [0], [dnl -racoon: reload -setkey: -> spddelete 0.0.0.0/0 1.2.3.4 gre -P out; -> spddelete 1.2.3.4 0.0.0.0/0 gre -P in; -setkey: -> dump ; -setkey: -> dump ; -]) -AT_CHECK([trim etc/racoon/psk.txt], [0], []) -AT_CHECK([trim etc/racoon/racoon.conf], [0], [dnl -path pre_shared_key "/etc/racoon/psk.txt"; -path certificate "/etc/racoon/certs"; -sainfo anonymous { - pfs_group 2; - lifetime time 1 hour; - encryption_algorithm aes; - authentication_algorithm hmac_sha1, hmac_md5; - compression_algorithm deflate; -} -]) - -### -### Add ipsec_gre certificate interface and check what ovs-monitor-ipsec does -### -AT_DATA([cert.pem], [dnl ------BEGIN CERTIFICATE----- -(not a real certificate) ------END CERTIFICATE----- + + +# WAIT_FOR_TUNNEL_TO_UPDATE([tunnel], [version]) +# +# This macro should be called after ovs-vsctl commands that updated +# IPsec configuration in OVSDB. It basically serves as barrier to ensures +# that ovs-monitor-ipsec daemon's internal configuration for 'tunnel' has +# updated to 'version' +m4_define([WAIT_FOR_TUNNEL_TO_UPDATE], [ +OVS_WAIT_UNTIL([ovs-appctl -t ovs-monitor-ipsec tunnels/show | grep "Interface name: $1 v$2"]) ]) -AT_DATA([key.pem], [dnl + + +# 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 fake 'ipsec' utility +# in 'actions' file. It filters out "ipsec rereadsecrets" and "ipsec status" +# commands. +m4_define([VERIFY_IPSEC_COMMANDS_EXECUTED], [ +AT_CHECK([cat actions|egrep -v "ipsec status|ipsec rereadsecrets"], [0], [$1]) +]) + + +# OVS_MONITOR_IPSEC_STOP([whitelist]) +# +# This macro gracefully stops all daemoms that were started by +# OVS_MONITOR_IPSEC_START. It passed down "whitelist" to OVS_VSWITCHD_STOP +# to prevent tests failing on expected error messages. +m4_define([OVS_MONITOR_IPSEC_STOP], [ +AT_CHECK([ovs-appctl -t ovs-monitor-ipsec exit]) +OVS_VSWITCHD_STOP([$1]) +trap '' 0]) + +# INIT_SSL_CERTS([]) +# +# This macro creates sample SSL certificates so that it would be +# possible to test tunnels that use "use_ssl_certs" setting. +m4_define([INIT_SSL_CERTS], + [AT_DATA([ssl_key.pem], [ -----BEGIN RSA PRIVATE KEY----- -(not a real 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_CHECK([ovs_vsctl \ - -- add-port br0 gre1 \ - -- set Interface gre1 type=ipsec_gre \ - options:remote_ip=2.3.4.5 \ - options:peer_cert='"-----BEGIN CERTIFICATE----- -(not a real peer certificate) ------END CERTIFICATE----- -"' \ - options:certificate='"/cert.pem"' \ - options:private_key='"/key.pem"']) -OVS_WAIT_UNTIL([test `wc -l < actions` -ge 21]) -AT_CHECK([sed '1,17d' actions], [0], [dnl -racoon: reload -setkey: -> spdadd 0.0.0.0/0 2.3.4.5 gre -P out ipsec esp/transport//require; -> spdadd 2.3.4.5 0.0.0.0/0 gre -P in ipsec esp/transport//require; -]) -AT_CHECK([trim etc/racoon/psk.txt], [0], []) -AT_CHECK([trim etc/racoon/racoon.conf], [0], [dnl -path pre_shared_key "/etc/racoon/psk.txt"; -path certificate "/etc/racoon/certs"; -remote 2.3.4.5 { - exchange_mode main; - nat_traversal on; - ike_frag on; - certificate_type x509 "/cert.pem" "/key.pem"; - my_identifier asn1dn; - peers_identifier asn1dn; - peers_certfile x509 "/etc/racoon/certs/ovs-2.3.4.5.pem"; - verify_identifier on; - proposal { - encryption_algorithm aes; - hash_algorithm sha1; - authentication_method rsasig; - dh_group 2; - } -} -sainfo anonymous { - pfs_group 2; - lifetime time 1 hour; - encryption_algorithm aes; - authentication_algorithm hmac_sha1, hmac_md5; - compression_algorithm deflate; -} -]) -AT_CHECK([cat etc/racoon/certs/ovs-2.3.4.5.pem], [0], [dnl + AT_DATA([ssl_cert.pem], [ -----BEGIN CERTIFICATE----- -(not a real peer 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]) +]) -### -### Delete the ipsec_gre certificate interface. -### -AT_CHECK([ovs_vsctl del-port gre1]) -OVS_WAIT_UNTIL([test `wc -l < actions` -ge 29]) -AT_CHECK([sed '1,21d' actions], [0], [dnl -racoon: reload -setkey: -> spddelete 0.0.0.0/0 2.3.4.5 gre -P out; -> spddelete 2.3.4.5 0.0.0.0/0 gre -P in; -setkey: -> dump ; -setkey: -> dump ; -]) -AT_CHECK([trim etc/racoon/psk.txt], [0], []) -AT_CHECK([trim etc/racoon/racoon.conf], [0], [dnl -path pre_shared_key "/etc/racoon/psk.txt"; -path certificate "/etc/racoon/certs"; -sainfo anonymous { - pfs_group 2; - lifetime time 1 hour; - encryption_algorithm aes; - authentication_algorithm hmac_sha1, hmac_md5; - compression_algorithm deflate; -} -]) -AT_CHECK([test ! -f etc/racoon/certs/ovs-2.3.4.5.pem]) - -### -### Add an SSL certificate interface. -### -cp cert.pem ssl-cert.pem -cp key.pem ssl-key.pem -AT_DATA([ssl-cacert.pem], [dnl ------BEGIN CERTIFICATE----- -(not a real CA certificate) ------END CERTIFICATE----- + + +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' ]) -AT_CHECK([ovs_vsctl set-ssl /ssl-key.pem /ssl-cert.pem /ssl-cacert.pem \ - -- add-port br0 gre2 \ - -- set Interface gre2 type=ipsec_gre \ - options:remote_ip=3.4.5.6 \ - options:peer_cert='"-----BEGIN CERTIFICATE----- -(not a real peer certificate) ------END CERTIFICATE----- -"' \ - options:use_ssl_cert='"true"']) -OVS_WAIT_UNTIL([test `wc -l < actions` -ge 33]) -AT_CHECK([sed '1,29d' actions], [0], [dnl -racoon: reload -setkey: -> spdadd 0.0.0.0/0 3.4.5.6 gre -P out ipsec esp/transport//require; -> spdadd 3.4.5.6 0.0.0.0/0 gre -P in ipsec esp/transport//require; -]) -AT_CHECK([trim etc/racoon/psk.txt], [0], []) -AT_CHECK([trim etc/racoon/racoon.conf], [0], [dnl -path pre_shared_key "/etc/racoon/psk.txt"; -path certificate "/etc/racoon/certs"; -remote 3.4.5.6 { - exchange_mode main; - nat_traversal on; - ike_frag on; - certificate_type x509 "/ssl-cert.pem" "/ssl-key.pem"; - my_identifier asn1dn; - peers_identifier asn1dn; - peers_certfile x509 "/etc/racoon/certs/ovs-3.4.5.6.pem"; - verify_identifier on; - proposal { - encryption_algorithm aes; - hash_algorithm sha1; - authentication_method rsasig; - dh_group 2; - } -} -sainfo anonymous { - pfs_group 2; - lifetime time 1 hour; - encryption_algorithm aes; - authentication_algorithm hmac_sha1, hmac_md5; - compression_algorithm deflate; -} -]) -AT_CHECK([cat etc/racoon/certs/ovs-3.4.5.6.pem], [0], [dnl ------BEGIN CERTIFICATE----- -(not a real peer certificate) ------END CERTIFICATE----- +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 + -### -### Delete the SSL certificate interface. -### -AT_CHECK([ovs_vsctl del-port gre2]) -OVS_WAIT_UNTIL([test `wc -l < actions` -ge 41]) -AT_CHECK([sed '1,33d' actions], [0], [dnl -racoon: reload -setkey: -> spddelete 0.0.0.0/0 3.4.5.6 gre -P out; -> spddelete 3.4.5.6 0.0.0.0/0 gre -P in; -setkey: -> dump ; -setkey: -> dump ; -]) -AT_CHECK([trim etc/racoon/psk.txt], [0], []) -AT_CHECK([trim etc/racoon/racoon.conf], [0], [dnl -path pre_shared_key "/etc/racoon/psk.txt"; -path certificate "/etc/racoon/certs"; -sainfo anonymous { - pfs_group 2; - lifetime time 1 hour; - encryption_algorithm aes; - authentication_algorithm hmac_sha1, hmac_md5; - compression_algorithm deflate; -} -]) -AT_CHECK([test ! -f etc/racoon/certs/ovs-3.4.5.6.pem]) - -OVSDB_SERVER_SHUTDOWN + +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_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 propoer 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 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_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]) +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 +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_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]) +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 +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_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]) +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 +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_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]) +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 +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_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]) +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 +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_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]) +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 +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_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]) +WAIT_FOR_TUNNEL_TO_UPDATE([stt0], [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_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 +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_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]) +WAIT_FOR_TUNNEL_TO_UPDATE([stt0], [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_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 +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_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]) +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]) +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]) +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]) +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]) +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]) +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]) +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 'tun0' must be ignored +OVS_MONITOR_IPSEC_STOP(["/tun0/d"]) +AT_CLEANUP \ No newline at end of file diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml index 8a60474..667b36a 100644 --- a/vswitchd/vswitch.xml +++ b/vswitchd/vswitch.xml @@ -1836,6 +1836,12 @@ to be extended in the future. </dd> + <dt><code>ipsec_geneve</code></dt> + <dd> + An Ethernet over Geneve (<code>http://tools.ietf.org/html/draft-gross-geneve-00</code>) + IPv4 tunnel over IPsec tunnel. + </dd> + <dt><code>gre</code></dt> <dd> An Ethernet over RFC 2890 Generic Routing Encapsulation over IPv4 @@ -1876,6 +1882,12 @@ </p> </dd> + <dt><code>ipsec_vxlan</code></dt> + <dd> + An Ethernet over RFC 7348 Virtual eXtensible Local Area Network + (VXLAN) over IPv4 IPsec tunnel. + </dd> + <dt><code>lisp</code></dt> <dd> <p> @@ -1911,6 +1923,11 @@ available in kernel datapath on kernel 3.5 or newer. </dd> + <dt><code>ipsec_stt</code></dt> + <dd> + An Ethernet over <code>STT</code> over IPv4 IPsec tunnel. + </dd> + <dt><code>patch</code></dt> <dd> A pair of virtual devices that act as a patch cable. -- 2.1.4 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev