[ovs-dev] [PATCH 6/6] vswitch: Add support for IPsec certificate authentication.
Justin Pettit
jpettit at nicira.com
Wed Dec 22 08:04:34 UTC 2010
Previously, it was possible to fake configuring the use of certificate
authentication for IPsec, but it really just used a static pre-shared key
behind the scenes. This commit publicly mentions certificate
authentication and finally does the real work behind the scenes.
---
debian/ovs-monitor-ipsec | 249 ++++++++++++++++++++++++++++++---------------
lib/netdev-vport.c | 26 ++++-
vswitchd/vswitch.xml | 46 ++++++--
3 files changed, 220 insertions(+), 101 deletions(-)
diff --git a/debian/ovs-monitor-ipsec b/debian/ovs-monitor-ipsec
index fc35ddb..44d5ba4 100755
--- a/debian/ovs-monitor-ipsec
+++ b/debian/ovs-monitor-ipsec
@@ -20,7 +20,6 @@
# xxx To-do:
# - Doesn't actually check that Interface is connected to bridge
-# - Doesn't support cert authentication
import getopt
@@ -53,35 +52,61 @@ setkey = "/usr/sbin/setkey"
class Racoon:
# Default locations for files
conf_file = "/etc/racoon/racoon.conf"
- cert_file = "/etc/racoon/certs"
+ cert_dir = "/etc/racoon/certs"
psk_file = "/etc/racoon/psk.txt"
- # Default racoon configuration file we use for IKE
- conf_template = """# Configuration file generated by Open vSwitch
+ # Racoon configuration header we use for IKE
+ conf_header = """# Configuration file generated by Open vSwitch
#
# Do not modify by hand!
-path pre_shared_key "/etc/racoon/psk.txt";
-path certificate "/etc/racoon/certs";
+path pre_shared_key "%s";
+path certificate "%s";
-remote anonymous {
+"""
+
+ # 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;
+ 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 pre_shared_key;
+ 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;
+"""
+
+ # 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):
@@ -89,39 +114,91 @@ sainfo anonymous {
self.cert_hosts = {}
# Replace racoon's conf file with our template
- f = open(Racoon.conf_file, "w")
- f.write(Racoon.conf_template)
- f.close()
-
- # Clear out any pre-shared keys
- self.commit_psk()
-
- self.reload()
+ self.commit()
def reload(self):
exitcode = subprocess.call(["/etc/init.d/racoon", "reload"])
if exitcode != 0:
s_log.warning("couldn't reload racoon")
- def commit_psk(self):
- f = open(Racoon.psk_file, 'w')
-
- # The file must only be accessible by root
+ def commit(self):
+ # Rewrite the Racoon configuration file
+ conf_file = open(Racoon.conf_file, 'w')
+ conf_file.write(Racoon.conf_header % (Racoon.psk_file, Racoon.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.keys():
+ 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.
+ psk_file = open(Racoon.psk_file, 'w')
os.chmod(Racoon.psk_file, stat.S_IRUSR | stat.S_IWUSR)
- f.write("# Generated by Open vSwitch...do not modify by hand!\n\n")
- for host, psk in self.psk_hosts.iteritems():
- f.write("%s %s\n" % (host, psk))
- f.close()
+ 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_psk()
+ self.commit()
- def del_psk(self, host):
+ def add_cert(self, host, vals):
if host in self.psk_hosts:
+ raise error.Error("host %s already defined for psk" % host)
+
+ if "certificate" not in vals:
+ raise error.Error("'certificate' not defined for %s" % host)
+ elif "private_key" not in vals:
+ # Assume the private key is stored in the same PEM file as
+ # the certificate
+ vals["private_key"] = vals["certificate"]
+
+ # 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 = Racoon.cert_dir + "/ovs-" + host + ".pem"
+ f = open(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()
+
+ 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(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_psk()
+ self.commit()
# Class to configure IPsec on a system using racoon for IKE and setkey
@@ -132,6 +209,7 @@ class IPsec:
self.sad_flush()
self.spd_flush()
self.racoon = Racoon()
+ self.entries = []
def call_setkey(self, cmds):
try:
@@ -203,25 +281,24 @@ class IPsec:
cmds += "spddelete %s %s gre -P in;" % (remote_ip, local_ip)
self.call_setkey(cmds)
- def ipsec_cert_del(self, local_ip, remote_ip):
- # Need to support cert...right now only PSK supported
- self.racoon.del_psk(remote_ip)
- self.spd_del(local_ip, remote_ip)
- self.sad_del(local_ip, remote_ip)
+ 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)
- def ipsec_cert_update(self, local_ip, remote_ip, cert):
- # Need to support cert...right now only PSK supported
- self.racoon.add_psk(remote_ip, "abc12345")
+ self.racoon.add_entry(remote_ip, vals)
self.spd_add(local_ip, remote_ip)
- def ipsec_psk_del(self, local_ip, remote_ip):
- self.racoon.del_psk(remote_ip)
- self.spd_del(local_ip, remote_ip)
- self.sad_del(local_ip, remote_ip)
+ self.entries.append(remote_ip)
- def ipsec_psk_update(self, local_ip, remote_ip, psk):
- self.racoon.add_psk(remote_ip, psk)
- self.spd_add(local_ip, 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 keep_table_columns(schema, table_name, column_types):
@@ -266,6 +343,26 @@ def usage():
print " -h, --help display this help message"
sys.exit(0)
+def update_ipsec(ipsec, interfaces, new_interfaces):
+ for name, vals in interfaces.items():
+ if name not in new_interfaces.keys():
+ ipsec.del_entry(vals["local_ip"], vals["remote_ip"])
+
+ for name, vals in new_interfaces.items():
+ orig_vals = interfaces.get(name)
+ if orig_vals:
+ # Configuration for this host already exists. If
+ # it has changed, this is an error.
+ if vals != orig_vals:
+ s_log.warning("configuration changed for %s, need to delete "
+ "interface first" % name)
+ 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))
+
def main(argv):
try:
options, args = getopt.gnu_getopt(
@@ -306,44 +403,30 @@ def main(argv):
new_interfaces = {}
for rec in idl.data["Interface"].itervalues():
- name = rec.name.as_scalar()
- ipsec_cert = rec.options.get("ipsec_cert")
- ipsec_psk = rec.options.get("ipsec_psk")
- is_ipsec = ipsec_cert or ipsec_psk
-
if rec.type.as_scalar() == "ipsec_gre":
- if ipsec_cert or ipsec_psk:
- new_interfaces[name] = {
- "remote_ip": rec.options.get("remote_ip"),
- "local_ip": rec.options.get("local_ip", "0.0.0.0/0"),
- "ipsec_cert": ipsec_cert,
- "ipsec_psk": ipsec_psk }
- else:
- s_log.warning(
- "no ipsec_cert or ipsec_psk defined for %s" % name)
-
- if interfaces != new_interfaces:
- for name, vals in interfaces.items():
- if name not in new_interfaces.keys():
- ipsec.ipsec_cert_del(vals["local_ip"], vals["remote_ip"])
- for name, vals in new_interfaces.items():
- orig_vals = interfaces.get(name):
- if orig_vals:
- # Configuration for this host already exists. If
- # it has changed, this is an error.
- if vals != orig_vals:
- s_log.warning(
- "configuration changed for %s, need to delete "
- "interface first" % name)
- continue
+ name = rec.name.as_scalar()
+ peer_cert = rec.options.get("peer_cert")
+ psk = rec.options.get("psk")
- if vals["ipsec_cert"]:
- ipsec.ipsec_cert_update(vals["local_ip"],
- vals["remote_ip"], vals["ipsec_cert"])
- else vals["ipsec_psk"]:
- ipsec.ipsec_psk_update(vals["local_ip"],
- vals["remote_ip"], vals["ipsec_psk"])
+ if peer_cert and psk:
+ s_log.warning("both 'peer_cert' and 'psk' defined for %s"
+ % name)
+ continue
+ elif not peer_cert and not psk:
+ s_log.warning("no 'peer_cert' or 'psk' defined for %s"
+ % name)
+ continue
+ new_interfaces[name] = {
+ "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"),
+ "peer_cert": peer_cert,
+ "psk": psk }
+
+ if interfaces != new_interfaces:
+ update_ipsec(ipsec, interfaces, new_interfaces)
interfaces = new_interfaces
if __name__ == '__main__':
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index ec02917..9502ed2 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -514,8 +514,15 @@ parse_tunnel_config(const struct netdev_dev *dev, const struct shash *args,
if (!strcmp(node->data, "false")) {
config.flags &= ~TNL_F_HDR_CACHE;
}
- } else if ((!strcmp(node->name, "ipsec_cert")
- || !strcmp(node->name, "ipsec_psk")) && is_ipsec) {
+ } else if (!strcmp(node->name, "peer_cert") && is_ipsec) {
+ if (shash_find(args, "certificate")) {
+ ipsec_mech_set = true;
+ } else {
+ VLOG_WARN("%s: 'peer_cert' requires 'certificate' argument",
+ name);
+ return EINVAL;
+ }
+ } else if (!strcmp(node->name, "psk") && is_ipsec) {
ipsec_mech_set = true;
} else {
VLOG_WARN("%s: unknown %s argument '%s'",
@@ -523,10 +530,17 @@ parse_tunnel_config(const struct netdev_dev *dev, const struct shash *args,
}
}
- if (is_ipsec && !ipsec_mech_set) {
- VLOG_WARN("%s: IPsec requires an 'ipsec_cert' or ipsec_psk' argument",
- name);
- return EINVAL;
+ if (is_ipsec) {
+ if (shash_find(args, "peer_cert") && shash_find(args, "psk")) {
+ VLOG_WARN("%s: cannot define both 'peer_cert' and 'psk'", name);
+ return EINVAL;
+ }
+
+ if (!ipsec_mech_set) {
+ VLOG_WARN("%s: IPsec requires an 'peer_cert' or psk' argument",
+ name);
+ return EINVAL;
+ }
}
if (!config.daddr) {
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index 2dd6c93..7f47bc3 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -706,15 +706,16 @@
</dl>
</dd>
<dt><code>ipsec_gre</code></dt>
- <dd>An Ethernet over RFC 2890 Generic Routing Encapsulation over
- IPv4 IPsec tunnel. Each tunnel (including those of type
- <code>gre</code>) must be uniquely identified by the
- combination of <code>remote_ip</code> and
- <code>local_ip</code>. Note that if two ports are defined
- that are the same except one has an optional identifier and
- the other does not, the more specific one is matched first.
- The following options may be specified in the
- <ref column="options"/> column:
+ <dd>An Ethernet over RFC 2890 Generic Routing Encapsulation
+ over IPv4 IPsec tunnel. Each tunnel (including those of type
+ <code>gre</code>) must be uniquely identified by the
+ combination of <code>remote_ip</code> and
+ <code>local_ip</code>. Note that if two ports are defined
+ that are the same except one has an optional identifier and
+ the other does not, the more specific one is matched first.
+ An authentication method of <code>peer_cert</code> or
+ <code>psk</code> must be defined. The following options may
+ be specified in the <ref column="options"/> column:
<dl>
<dt><code>remote_ip</code></dt>
<dd>Required. The tunnel endpoint.</dd>
@@ -725,9 +726,30 @@
match. Default is to match all addresses.</dd>
</dl>
<dl>
- <dt><code>ipsec_psk</code></dt>
- <dd>Required. Specifies a pre-shared key for authentication
- that must be identical on both sides of the tunnel.</dd>
+ <dt><code>peer_cert</code></dt>
+ <dd>Required for certificate authentication. A string
+ containing the peer's certificate in PEM format.
+ Additionally the host's certificate must be specified
+ with the <code>certificate</code> option.</dd>
+ </dl>
+ <dl>
+ <dt><code>certificate</code></dt>
+ <dd>Required for certificate authentication. The name of a
+ PEM file containing a certificate that will be presented
+ to the peer during authentication.</dd>
+ </dl>
+ <dl>
+ <dt><code>private_key</code></dt>
+ <dd>Optional for certificate authentication. The name of
+ a PEM file containing the private key associated with
+ <code>certificate</code>. If <code>certificate</code>
+ contains the private key, this option may be omitted.</dd>
+ </dl>
+ <dl>
+ <dt><code>psk</code></dt>
+ <dd>Required for pre-shared key authentication. Specifies a
+ pre-shared key for authentication that must be identical on
+ both sides of the tunnel.</dd>
</dl>
<dl>
<dt><code>in_key</code></dt>
--
1.7.1
More information about the dev
mailing list