ovs-monitor-ipsec: Add unit test.
authorBen Pfaff <blp@nicira.com>
Fri, 23 Sep 2011 21:21:19 +0000 (14:21 -0700)
committerBen Pfaff <blp@nicira.com>
Mon, 26 Sep 2011 20:08:58 +0000 (13:08 -0700)
debian/ovs-monitor-ipsec
tests/automake.mk
tests/ovs-monitor-ipsec.at [new file with mode: 0644]
tests/testsuite.at

index 10b278d..5aca0f7 100755 (executable)
@@ -53,7 +53,9 @@ try:
 except socket.error, e:
     logging.basicConfig()
     s_log.warn("failed to connect to syslog (%s)" % e)
+s_log.addHandler(logging.StreamHandler())
 
+root_prefix = ''                # Prefix for absolute file names, for testing.
 setkey = "/usr/sbin/setkey"
 
 
@@ -123,11 +125,12 @@ path certificate "%s";
         self.psk_hosts = {}
         self.cert_hosts = {}
 
-        if not os.path.isdir(self.cert_dir):
+        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/ovs-*.pem" % self.cert_dir):
+        for ovs_cert in glob.glob("%s%s/ovs-*.pem"
+                                  % (root_prefix, self.cert_dir)):
             try:
                 os.remove(ovs_cert)
             except OSError:
@@ -137,20 +140,22 @@ path certificate "%s";
         self.commit()
 
     def reload(self):
-        exitcode = subprocess.call(["/etc/init.d/racoon", "reload"])
+        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.
             s_log.warning("attempting to restart racoon")
-            exitcode = subprocess.call(["/etc/init.d/racoon", "restart"])
+            exitcode = subprocess.call([root_prefix + "/etc/init.d/racoon",
+                                        "restart"])
             if exitcode != 0:
                 s_log.warning("couldn't reload racoon")
 
     def commit(self):
         # Rewrite the Racoon configuration file
-        conf_file = open(self.conf_file, 'w')
+        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():
@@ -165,7 +170,7 @@ path certificate "%s";
 
         # Rewrite the pre-shared keys file; it must only be readable by root.
         orig_umask = os.umask(0077)
-        psk_file = open(Racoon.psk_file, 'w')
+        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!")
@@ -186,10 +191,10 @@ path certificate "%s";
     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(vals["certificate"]):
+        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(vals["private_key"]):
+        elif not os.path.isfile(root_prefix + vals["private_key"]):
             raise error.Error("'private_key' file does not exist: %s"
                     % vals["private_key"])
 
@@ -199,11 +204,11 @@ path certificate "%s";
         if vals["peer_cert"].find("-----BEGIN CERTIFICATE-----") == -1:
             raise error.Error("'peer_cert' is not in valid PEM format")
 
-        cert = open(vals["certificate"]).read()
+        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(vals["private_key"]).read()
+        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")
 
@@ -227,7 +232,7 @@ path certificate "%s";
         # The peer's certificate comes to us in PEM format as a string.
         # Write that string to a file for Racoon to use.
         peer_cert_file = "%s/ovs-%s.pem" % (self.cert_dir, host)
-        f = open(peer_cert_file, "w")
+        f = open(root_prefix + peer_cert_file, "w")
         f.write(vals["peer_cert"])
         f.close()
 
@@ -241,7 +246,7 @@ path certificate "%s";
         del self.cert_hosts[host]
         self.commit()
         try:
-            os.remove(peer_cert_file)
+            os.remove(root_prefix + peer_cert_file)
         except OSError:
             pass
 
@@ -271,10 +276,11 @@ class IPsec:
 
     def call_setkey(self, cmds):
         try:
-            p = subprocess.Popen([setkey, "-c"], stdin=subprocess.PIPE,
-                    stdout=subprocess.PIPE)
+            p = subprocess.Popen([root_prefix + setkey, "-c"],
+                                 stdin=subprocess.PIPE,
+                                 stdout=subprocess.PIPE)
         except:
-            s_log.error("could not call setkey")
+            s_log.error("could not call %s%s" % (root_prefix, setkey))
             sys.exit(1)
 
         # xxx It is safer to pass the string into the communicate()
@@ -290,7 +296,7 @@ class IPsec:
         # older entry could be in a "dying" state.
         spi_list = []
         host_line = "%s %s" % (local_ip, remote_ip)
-        results = self.call_setkey("dump ;").split("\n")
+        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
@@ -301,7 +307,7 @@ class IPsec:
         return spi_list
 
     def sad_flush(self):
-        self.call_setkey("flush;")
+        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
@@ -323,18 +329,18 @@ class IPsec:
             self.call_setkey(cmds)
 
     def spd_flush(self):
-        self.call_setkey("spdflush;")
+        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;" %
+        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;" % (remote_ip, local_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):
@@ -403,8 +409,10 @@ def usage():
     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
     print "where DATABASE is a socket on which ovsdb-server is listening."
     ovs.daemon.usage()
-    print "Other options:"
-    print "  -h, --help               display this help message"
+    print """\
+Other options:
+    --root-prefix=DIR   Use DIR as alternate root directory (for testing).
+    -h, --help          Display this help message."""
     sys.exit(0)
 
 
@@ -441,7 +449,7 @@ def get_ssl_cert(data):
 def main(argv):
     try:
         options, args = getopt.gnu_getopt(
-            argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
+            argv[1:], 'h', ['help', 'root-prefix='] + ovs.daemon.LONG_OPTIONS)
     except getopt.GetoptError, geo:
         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
         sys.exit(1)
@@ -449,6 +457,9 @@ def main(argv):
     for key, value in options:
         if key in ['-h', '--help']:
             usage()
+        elif key == "--root-prefix":
+            global root_prefix
+            root_prefix = value
         elif not ovs.daemon.parse_opt(key, value):
             sys.stderr.write("%s: unhandled option %s\n"
                              % (ovs.util.PROGRAM_NAME, key))
index 9b094de..dcf6026 100644 (file)
@@ -51,6 +51,7 @@ TESTSUITE_AT = \
        tests/ovsdb-monitor.at \
        tests/ovsdb-idl.at \
        tests/ovs-vsctl.at \
+       tests/ovs-monitor-ipsec.at \
        tests/interface-reconfigure.at
 TESTSUITE = $(srcdir)/tests/testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal
diff --git a/tests/ovs-monitor-ipsec.at b/tests/ovs-monitor-ipsec.at
new file mode 100644 (file)
index 0000000..ad1e96e
--- /dev/null
@@ -0,0 +1,222 @@
+AT_BANNER([ovs-monitor-ipsec])
+
+AT_SETUP([ovs-monitor-ipsec])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+
+OVS_PKGDATADIR=`pwd`; export OVS_PKGDATADIR
+cp "$top_srcdir/vswitchd/vswitch.ovsschema" .
+
+trap 'kill `cat pid ovs-monitor-ipsec.pid`' 0
+
+mkdir etc etc/init.d etc/racoon etc/racoon/certs
+mkdir usr usr/sbin
+
+AT_DATA([etc/init.d/racoon], [dnl
+#! /bin/sh
+echo "racoon: $@" >&3
+exit 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 --timeout=5 --no-wait -vreconnect:ANY: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-name=`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-----
+])
+AT_DATA([key.pem], [dnl
+-----BEGIN RSA PRIVATE KEY-----
+(not a real private key)
+-----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
+-----BEGIN CERTIFICATE-----
+(not a real peer certificate)
+-----END CERTIFICATE-----
+])
+
+###
+### 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])
+
+AT_CLEANUP
index ea5208f..19b7802 100644 (file)
@@ -64,4 +64,5 @@ m4_include([tests/ofproto.at])
 m4_include([tests/ofproto-dpif.at])
 m4_include([tests/ovsdb.at])
 m4_include([tests/ovs-vsctl.at])
+m4_include([tests/ovs-monitor-ipsec.at])
 m4_include([tests/interface-reconfigure.at])