X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=debian%2Fovs-monitor-ipsec;h=94048047ce2e3d51e374a4fa4557793e26522906;hb=e0edde6fee279cdbbf3c179f5f50adaf0c7c7f1e;hp=12ff9f5f2f75e4b86e1262be9397cbe1e3e1c254;hpb=f916d1ccdc02843d1564c9c227b3b8aead8b3a06;p=sliver-openvswitch.git diff --git a/debian/ovs-monitor-ipsec b/debian/ovs-monitor-ipsec index 12ff9f5f2..94048047c 100755 --- a/debian/ovs-monitor-ipsec +++ b/debian/ovs-monitor-ipsec @@ -1,5 +1,5 @@ #!/usr/bin/python -# Copyright (c) 2009, 2010 Nicira Networks +# Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,31 +25,32 @@ # adding an interface to racoon.conf. -import getopt +import argparse import glob -import logging, logging.handlers import os import subprocess import sys +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.unixctl +import ovs.vlog +vlog = ovs.vlog.Vlog("ovs-monitor-ipsec") +root_prefix = '' # Prefix for absolute file names, for testing. +SETKEY = "/usr/sbin/setkey" +exiting = False -# By default log messages as DAEMON into syslog -s_log = logging.getLogger("ovs-monitor-ipsec") -l_handler = logging.handlers.SysLogHandler( - "/dev/log", - facility=logging.handlers.SysLogHandler.LOG_DAEMON) -l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s') -l_handler.setFormatter(l_formatter) -s_log.addHandler(l_handler) +def unixctl_exit(conn, unused_argv, unused_aux): + global exiting + exiting = True + conn.reply(None) -setkey = "/usr/sbin/setkey" # Class to configure the racoon daemon, which handles IKE negotiation class Racoon: @@ -83,6 +84,7 @@ path certificate "%s"; cert_entry = """remote %s { exchange_mode main; nat_traversal on; + ike_frag on; certificate_type x509 "%s" "%s"; my_identifier asn1dn; peers_identifier asn1dn; @@ -116,31 +118,37 @@ path certificate "%s"; 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/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: - s_log.warning("couldn't remove %s" % ovs_cert) + vlog.warn("couldn't remove %s" % ovs_cert) # Replace racoon's conf file with our template 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 it's configuration file and will + # 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"]) + vlog.warn("attempting to restart racoon") + exitcode = subprocess.call([root_prefix + "/etc/init.d/racoon", + "restart"]) if exitcode != 0: - s_log.warning("couldn't reload racoon") + vlog.warn("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(): @@ -155,7 +163,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!") @@ -176,10 +184,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"]) @@ -189,14 +197,13 @@ 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") - def _add_cert(self, host, vals): if host in self.psk_hosts: @@ -205,7 +212,7 @@ path certificate "%s"; 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 + # 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 @@ -217,13 +224,10 @@ 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 + vals["peer_cert_file"], "w") f.write(vals["peer_cert"]) f.close() - vals["peer_cert_file"] = peer_cert_file - self.cert_hosts[host] = vals self.commit() @@ -232,7 +236,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 @@ -262,10 +266,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") + vlog.err("could not call %s%s" % (root_prefix, SETKEY)) sys.exit(1) # xxx It is safer to pass the string into the communicate() @@ -281,18 +286,18 @@ 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 - spi_line = results[i+1] + 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;") + 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 @@ -314,18 +319,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): @@ -338,7 +343,6 @@ class IPsec: 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) @@ -348,55 +352,6 @@ class IPsec: self.entries.remove(remote_ip) -def keep_table_columns(schema, table_name, column_types): - table = schema.tables.get(table_name) - if not table: - raise error.Error("schema has no %s table" % table_name) - - new_columns = {} - for column_name, column_type in column_types.iteritems(): - column = table.columns.get(column_name) - if not column: - raise error.Error("%s table schema lacks %s column" - % (table_name, column_name)) - if column.type != column_type: - raise error.Error("%s column in %s table has type \"%s\", " - "expected type \"%s\"" - % (column_name, table_name, - column.type.toEnglish(), - column_type.toEnglish())) - new_columns[column_name] = column - table.columns = new_columns - return table - -def monitor_uuid_schema_cb(schema): - string_type = types.Type(types.BaseType(types.StringType)) - optional_ssl_type = types.Type(types.BaseType(types.UuidType, - ref_table='SSL'), None, 0, 1) - string_map_type = types.Type(types.BaseType(types.StringType), - types.BaseType(types.StringType), - 0, sys.maxint) - - new_tables = {} - new_tables["Interface"] = keep_table_columns( - schema, "Interface", {"name": string_type, - "type": string_type, - "options": string_map_type}) - new_tables["Open_vSwitch"] = keep_table_columns( - schema, "Open_vSwitch", {"ssl": optional_ssl_type}) - new_tables["SSL"] = keep_table_columns( - schema, "SSL", {"certificate": string_type, - "private_key": string_type}) - schema.tables = new_tables - -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" - sys.exit(0) - def update_ipsec(ipsec, interfaces, new_interfaces): for name, vals in interfaces.iteritems(): if name not in new_interfaces: @@ -406,110 +361,133 @@ def update_ipsec(ipsec, interfaces, new_interfaces): orig_vals = interfaces.get(name) if orig_vals: # Configuration for this host already exists. Check if it's - # changed. - if vals == orig_vals: - continue - else: + # 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: + continue try: ipsec.add_entry(vals["local_ip"], vals["remote_ip"], vals) except error.Error, msg: - s_log.warning("skipping ipsec config for %s: %s" % (name, msg)) + vlog.warn("skipping ipsec config for %s: %s" % (name, msg)) + def get_ssl_cert(data): - for ovs_rec in data["Open_vSwitch"].itervalues(): - if ovs_rec.ssl.as_list(): - ssl_rec = data["SSL"][ovs_rec.ssl.as_scalar()] - return (ssl_rec.certificate.as_scalar(), - ssl_rec.private_key.as_scalar()) + 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) return None -def main(argv): - try: - options, args = getopt.gnu_getopt( - argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS) - except getopt.GetoptError, geo: - sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg)) - sys.exit(1) - - for key, value in options: - if key in ['-h', '--help']: - usage() - elif not ovs.daemon.parse_opt(key, value): - sys.stderr.write("%s: unhandled option %s\n" - % (ovs.util.PROGRAM_NAME, key)) - sys.exit(1) - - if len(args) != 1: - sys.stderr.write("%s: exactly one nonoption argument is required " - "(use --help for help)\n" % ovs.util.PROGRAM_NAME) - sys.exit(1) - ovs.daemon.die_if_already_running() - - remote = args[0] - idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb) +def main(): + + parser = argparse.ArgumentParser() + parser.add_argument("database", metavar="DATABASE", + help="A socket on which ovsdb-server is listening.") + parser.add_argument("--root-prefix", metavar="DIR", + help="Use DIR as alternate root directory" + " (for testing).") + + ovs.vlog.add_args(parser) + ovs.daemon.add_args(parser) + args = parser.parse_args() + ovs.vlog.handle_args(args) + ovs.daemon.handle_args(args) + + global root_prefix + if args.root_prefix: + root_prefix = args.root_prefix + + remote = args.database + schema_helper = ovs.db.idl.SchemaHelper() + schema_helper.register_columns("Interface", ["name", "type", "options"]) + 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("exit", "", 0, 0, unixctl_exit, None) + error, unixctl_server = ovs.unixctl.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 while True: - if not idl.run(): + 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) - ssl_cert = get_ssl_cert(idl.data) - new_interfaces = {} - for rec in idl.data["Interface"].itervalues(): - if rec.type.as_scalar() == "ipsec_gre": - name = rec.name.as_scalar() + for rec in idl.tables["Interface"].rows.itervalues(): + if rec.type == "ipsec_gre": + name = rec.name + options = rec.options + peer_cert_name = "ovs-%s.pem" % (options.get("remote_ip")) entry = { - "remote_ip": rec.options.get("remote_ip"), - "local_ip": rec.options.get("local_ip", "0.0.0.0/0"), - "certificate": rec.options.get("certificate"), - "private_key": rec.options.get("private_key"), - "use_ssl_cert": rec.options.get("use_ssl_cert"), - "peer_cert": rec.options.get("peer_cert"), - "psk": rec.options.get("psk") } + "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"]: - s_log.warning("both 'peer_cert' and 'psk' defined for %s" - % name) + vlog.warn("both 'peer_cert' and 'psk' defined for %s" + % name) continue elif not entry["peer_cert"] and not entry["psk"]: - s_log.warning("no 'peer_cert' or 'psk' defined for %s" - % name) + 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: - s_log.warning("no valid SSL entry for %s" % name) + vlog.warn("no valid SSL entry for %s" % name) continue entry["certificate"] = ssl_cert[0] entry["private_key"] = ssl_cert[1] new_interfaces[name] = entry - + if interfaces != new_interfaces: update_ipsec(ipsec, interfaces, new_interfaces) interfaces = new_interfaces - + + unixctl_server.close() + idl.close() + + if __name__ == '__main__': try: - main(sys.argv) + main() except SystemExit: # Let system.exit() calls complete normally raise except: - s_log.exception("traceback") + vlog.exception("traceback") sys.exit(ovs.daemon.RESTART_EXIT_CODE)