[ovs-dev] [PATCH ovn 2/7] Remove IPSEC
numans at ovn.org
numans at ovn.org
Fri May 8 06:04:42 UTC 2020
From: Dave Tucker <dave at dtucker.co.uk>
This code is used from the OVS tree and is not required for compilation.
It should be removed so that no-one attempts to modify it.
Submitted-at: https://github.com/ovn-org/ovn/pull/38
Signed-off-by: Dave Tucker <dave at dtucker.co.uk>
Signed-off-by: Numan Siddique <numans at ovn.org>
---
Makefile.am | 1 -
ipsec/.gitignore | 1 -
ipsec/automake.mk | 11 -
ipsec/ovs-monitor-ipsec.in | 1235 ------------------------------------
4 files changed, 1248 deletions(-)
delete mode 100644 ipsec/.gitignore
delete mode 100644 ipsec/automake.mk
delete mode 100755 ipsec/ovs-monitor-ipsec.in
diff --git a/Makefile.am b/Makefile.am
index fbd4638a1..b75c12eff 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -497,7 +497,6 @@ include include/automake.mk
include third-party/automake.mk
include debian/automake.mk
include lib/ovsdb_automake.mk
-include ipsec/automake.mk
include rhel/automake.mk
include tutorial/automake.mk
include selinux/automake.mk
diff --git a/ipsec/.gitignore b/ipsec/.gitignore
deleted file mode 100644
index e4083913f..000000000
--- a/ipsec/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/ovs-monitor-ipsec
diff --git a/ipsec/automake.mk b/ipsec/automake.mk
deleted file mode 100644
index b157e7b7f..000000000
--- a/ipsec/automake.mk
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) 2017 Nicira, Inc.
-#
-# Copying and distribution of this file, with or without modification,
-# are permitted in any medium without royalty provided the copyright
-# notice and this notice are preserved. This file is offered as-is,
-# without warranty of any kind.
-
-scripts_SCRIPTS += ipsec/ovs-monitor-ipsec
-EXTRA_DIST += ipsec/ovs-monitor-ipsec.in
-FLAKE8_PYFILES += ipsec/ovs-monitor-ipsec.in
-CLEANFILES += ipsec/ovs-monitor-ipsec
diff --git a/ipsec/ovs-monitor-ipsec.in b/ipsec/ovs-monitor-ipsec.in
deleted file mode 100755
index 37e370324..000000000
--- a/ipsec/ovs-monitor-ipsec.in
+++ /dev/null
@@ -1,1235 +0,0 @@
-#! @PYTHON3@
-# Copyright (c) 2017 Nicira, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at:
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import argparse
-import re
-import subprocess
-import sys
-import copy
-import os
-from string import Template
-
-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"
-transp_tmpl = {"gre": Template("""\
-conn $ifname-$version
-$auth_section
- leftprotoport=gre
- rightprotoport=gre
-
-"""), "gre64": Template("""\
-conn $ifname-$version
-$auth_section
- leftprotoport=gre
- rightprotoport=gre
-
-"""), "geneve": Template("""\
-conn $ifname-in-$version
-$auth_section
- leftprotoport=udp/6081
- rightprotoport=udp
-
-conn $ifname-out-$version
-$auth_section
- leftprotoport=udp
- rightprotoport=udp/6081
-
-"""), "stt": Template("""\
-conn $ifname-in-$version
-$auth_section
- leftprotoport=tcp/7471
- rightprotoport=tcp
-
-conn $ifname-out-$version
-$auth_section
- leftprotoport=tcp
- rightprotoport=tcp/7471
-
-"""), "vxlan": Template("""\
-conn $ifname-in-$version
-$auth_section
- leftprotoport=udp/4789
- rightprotoport=udp
-
-conn $ifname-out-$version
-$auth_section
- leftprotoport=udp
- rightprotoport=udp/4789
-
-""")}
-vlog = ovs.vlog.Vlog("ovs-monitor-ipsec")
-exiting = False
-monitor = None
-xfrm = None
-
-
-class XFRM(object):
- """This class is a simple wrapper around ip-xfrm (8) command line
- utility. We are using this class only for informational purposes
- so that ovs-monitor-ipsec could verify that IKE keying daemon has
- installed IPsec policies and security associations into kernel as
- expected."""
-
- def __init__(self, ip_root_prefix):
- self.IP = ip_root_prefix + "/sbin/ip"
-
- def get_policies(self):
- """This function returns IPsec policies (from kernel) in a dictionary
- where <key> is destination IPv4 address and <value> is SELECTOR of
- the IPsec policy."""
- policies = {}
- proc = subprocess.Popen([self.IP, 'xfrm', 'policy'],
- stdout=subprocess.PIPE)
- while True:
- line = proc.stdout.readline().strip()
- if line == '':
- break
- a = line.split(" ")
- if len(a) >= 4 and a[0] == "src" and a[2] == "dst":
- dst = (a[3].split("/"))[0]
- if dst not in policies:
- policies[dst] = []
- policies[dst].append(line)
- src = (a[3].split("/"))[0]
- if src not 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 remote_ip not in securities:
- securities[remote_ip] = []
- securities[remote_ip].append(line)
- if local_ip not in securities:
- securities[local_ip] = []
- securities[local_ip].append(line)
- return securities
-
-
-class StrongSwanHelper(object):
- """This class does StrongSwan specific configurations."""
-
- STRONGSWAN_CONF = """%s
-charon.plugins.kernel-netlink.set_proto_port_transport_sa = yes
-charon.plugins.kernel-netlink.xfrm_ack_expires = 10
-charon.load_modular = yes
-charon.plugins.gcm.load = yes
-""" % (FILE_HEADER)
-
- CONF_HEADER = """%s
-config setup
- uniqueids=yes
-
-conn %%default
- keyingtries=%%forever
- type=transport
- keyexchange=ikev2
- auto=route
- ike=aes256gcm16-sha256-modp2048
- esp=aes256gcm16-modp2048
-
-""" % (FILE_HEADER)
-
- CA_SECTION = """ca ca_auth
- cacert=%s
-
-"""
-
- SHUNT_POLICY = """conn prevent_unencrypted_gre
- type=drop
- leftprotoport=gre
- mark={0}
-
-conn prevent_unencrypted_geneve
- type=drop
- leftprotoport=udp/6081
- mark={0}
-
-conn prevent_unencrypted_stt
- type=drop
- leftprotoport=tcp/7471
- mark={0}
-
-conn prevent_unencrypted_vxlan
- type=drop
- leftprotoport=udp/4789
- mark={0}
-
-"""
-
- auth_tmpl = {"psk": Template("""\
- left=0.0.0.0
- right=$remote_ip
- authby=psk"""),
- "pki_remote": Template("""\
- left=0.0.0.0
- right=$remote_ip
- leftid=$local_name
- rightid=$remote_name
- leftcert=$certificate
- rightcert=$remote_cert"""),
- "pki_ca": Template("""\
- left=0.0.0.0
- right=$remote_ip
- leftid=$local_name
- rightid=$remote_name
- leftcert=$certificate""")}
-
- def __init__(self, root_prefix):
- self.CHARON_CONF = root_prefix + "/etc/strongswan.d/ovs.conf"
- self.IPSEC = root_prefix + "/usr/sbin/ipsec"
- self.IPSEC_CONF = root_prefix + "/etc/ipsec.conf"
- self.IPSEC_SECRETS = root_prefix + "/etc/ipsec.secrets"
- self.conf_file = None
- self.secrets_file = None
-
- def restart_ike_daemon(self):
- """This function restarts StrongSwan."""
- 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()
-
- vlog.info("Restarting StrongSwan")
- subprocess.call([self.IPSEC, "restart"])
-
- def get_active_conns(self):
- """This function parses output from 'ipsec status' command.
- It returns dictionary where <key> is interface name (as in OVSDB)
- and <value> is another dictionary. This another dictionary
- uses strongSwan connection name as <key> and more detailed
- sample line from the parsed outpus as <value>. """
-
- conns = {}
- proc = subprocess.Popen([self.IPSEC, 'status'], stdout=subprocess.PIPE)
-
- while True:
- line = proc.stdout.readline().strip()
- if line == '':
- break
- tunnel_name = line.split(":")
- if len(tunnel_name) < 2:
- continue
- m = re.match(r"(.*)(-in-\d+|-out-\d+|-\d+).*", tunnel_name[0])
- if not m:
- continue
- ifname = m.group(1)
- if ifname not in conns:
- conns[ifname] = {}
- (conns[ifname])[tunnel_name[0]] = line
-
- return conns
-
- def config_init(self):
- self.conf_file = open(self.IPSEC_CONF, "w")
- self.secrets_file = open(self.IPSEC_SECRETS, "w")
- self.conf_file.write(self.CONF_HEADER)
- self.secrets_file.write(FILE_HEADER)
-
- def config_global(self, monitor):
- """Configure the global state of IPsec tunnels."""
- needs_refresh = False
-
- if monitor.conf_in_use != monitor.conf:
- monitor.conf_in_use = copy.deepcopy(monitor.conf)
- needs_refresh = True
-
- # Configure the shunt policy
- if monitor.conf_in_use["skb_mark"]:
- skb_mark = monitor.conf_in_use["skb_mark"]
- self.conf_file.write(self.SHUNT_POLICY.format(skb_mark))
-
- # Configure the CA cert
- if monitor.conf_in_use["pki"]["ca_cert"]:
- cacert = monitor.conf_in_use["pki"]["ca_cert"]
- self.conf_file.write(self.CA_SECTION % cacert)
-
- return needs_refresh
-
- def config_tunnel(self, tunnel):
- if tunnel.conf["psk"]:
- self.secrets_file.write('0.0.0.0 %s : PSK "%s"\n' %
- (tunnel.conf["remote_ip"], tunnel.conf["psk"]))
- auth_section = self.auth_tmpl["psk"].substitute(tunnel.conf)
- else:
- self.secrets_file.write("0.0.0.0 %s : RSA %s\n" %
- (tunnel.conf["remote_ip"],
- tunnel.conf["private_key"]))
- if tunnel.conf["remote_cert"]:
- tmpl = self.auth_tmpl["pki_remote"]
- auth_section = tmpl.substitute(tunnel.conf)
- else:
- tmpl = self.auth_tmpl["pki_ca"]
- auth_section = tmpl.substitute(tunnel.conf)
-
- vals = tunnel.conf.copy()
- vals["auth_section"] = auth_section
- vals["version"] = tunnel.version
- conf_text = transp_tmpl[tunnel.conf["tunnel_type"]].substitute(vals)
- self.conf_file.write(conf_text)
-
- def config_fini(self):
- self.secrets_file.close()
- self.conf_file.close()
- self.secrets_file = None
- self.conf_file = None
-
- def refresh(self, monitor):
- """This functions refreshes strongSwan configuration. Behind the
- scenes this function calls:
- 1. once "ipsec update" command that tells strongSwan to load
- all new tunnels from "ipsec.conf"; and
- 2. once "ipsec rereadsecrets" command that tells strongswan to load
- secrets from "ipsec.conf" file
- 3. for every removed tunnel "ipsec stroke down-nb <tunnel>" command
- that removes old tunnels.
- Once strongSwan vici bindings will be distributed with major
- Linux distributions this function could be simplified."""
- vlog.info("Refreshing StrongSwan configuration")
- subprocess.call([self.IPSEC, "update"])
- subprocess.call([self.IPSEC, "rereadsecrets"])
- # "ipsec update" command does not remove those tunnels that were
- # updated or that disappeared from the ipsec.conf file. So, we have
- # to manually remove them by calling "ipsec stroke down-nb <tunnel>"
- # command. We use <version> number to tell apart tunnels that
- # were just updated.
- # "ipsec down-nb" command is designed to be non-blocking (opposed
- # to "ipsec down" command). This means that we should not be concerned
- # about possibility of ovs-monitor-ipsec to block for each tunnel
- # while strongSwan sends IKE messages over Internet.
- conns_dict = self.get_active_conns()
- for ifname, conns in conns_dict.iteritems():
- tunnel = monitor.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 IndexError:
- vlog.err("%s does not contain version number")
- continue
- except ValueError:
- 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])
-
-
-class LibreSwanHelper(object):
- """This class does LibreSwan specific configurations."""
- CONF_HEADER = """%s
-config setup
- uniqueids=yes
-
-conn %%default
- keyingtries=%%forever
- type=transport
- auto=route
- ike=aes_gcm256-sha2_256
- esp=aes_gcm256
- ikev2=insist
-
-""" % (FILE_HEADER)
-
- SHUNT_POLICY = """conn prevent_unencrypted_gre
- type=drop
- left=%defaultroute
- leftprotoport=gre
- mark={0}
-
-conn prevent_unencrypted_geneve
- type=drop
- left=%defaultroute
- leftprotoport=udp/6081
- mark={0}
-
-conn prevent_unencrypted_stt
- type=drop
- left=%defaultroute
- leftprotoport=tcp/7471
- mark={0}
-
-conn prevent_unencrypted_vxlan
- type=drop
- left=%defaultroute
- leftprotoport=udp/4789
- mark={0}
-
-"""
-
- auth_tmpl = {"psk": Template("""\
- left=%defaultroute
- right=$remote_ip
- authby=secret"""),
- "pki_remote": Template("""\
- left=%defaultroute
- right=$remote_ip
- leftid=@$local_name
- rightid=@$remote_name
- leftcert="$local_name"
- rightcert="$remote_name"
- leftrsasigkey=%cert"""),
- "pki_ca": Template("""\
- left=%defaultroute
- right=$remote_ip
- leftid=@$local_name
- rightid=@$remote_name
- leftcert="ovs_certkey_$local_name"
- leftrsasigkey=%cert
- rightca=%same""")}
-
- CERT_PREFIX = "ovs_cert_"
- CERTKEY_PREFIX = "ovs_certkey_"
-
- def __init__(self, libreswan_root_prefix):
- self.IPSEC = libreswan_root_prefix + "/usr/sbin/ipsec"
- self.IPSEC_CONF = libreswan_root_prefix + "/etc/ipsec.conf"
- self.IPSEC_SECRETS = libreswan_root_prefix + "/etc/ipsec.secrets"
- self.conf_file = None
- self.secrets_file = None
-
- def restart_ike_daemon(self):
- """This function restarts LibreSwan."""
- # Remove the stale information from the NSS database
- self._nss_clear_database()
-
- 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()
-
- vlog.info("Restarting LibreSwan")
- subprocess.call([self.IPSEC, "restart"])
-
- def config_init(self):
- self.conf_file = open(self.IPSEC_CONF, "w")
- self.secrets_file = open(self.IPSEC_SECRETS, "w")
- self.conf_file.write(self.CONF_HEADER)
- self.secrets_file.write(FILE_HEADER)
-
- def config_global(self, monitor):
- """Configure the global state of IPsec tunnels."""
- needs_refresh = False
-
- if monitor.conf_in_use["pki"] != monitor.conf["pki"]:
- # Clear old state
- if monitor.conf_in_use["pki"]["certificate"]:
- local_name = monitor.conf_in_use["pki"]["local_name"]
- self._nss_delete_cert_and_key(self.CERTKEY_PREFIX + local_name)
-
- if monitor.conf_in_use["pki"]["ca_cert"]:
- self._nss_delete_cert(self.CERT_PREFIX + "cacert")
-
- # Load new state
- if monitor.conf["pki"]["certificate"]:
- cert = monitor.conf["pki"]["certificate"]
- key = monitor.conf["pki"]["private_key"]
- name = monitor.conf["pki"]["local_name"]
- name = self.CERTKEY_PREFIX + name
- self._nss_import_cert_and_key(cert, key, name)
-
- if monitor.conf["pki"]["ca_cert"]:
- self._nss_import_cert(monitor.conf["pki"]["ca_cert"],
- self.CERT_PREFIX + "cacert", 'CT,,')
-
- monitor.conf_in_use["pki"] = copy.deepcopy(monitor.conf["pki"])
- needs_refresh = True
-
- # Configure the shunt policy
- if monitor.conf["skb_mark"]:
- skb_mark = monitor.conf["skb_mark"]
- self.conf_file.write(self.SHUNT_POLICY.format(skb_mark))
-
- # Will update conf_in_use later in the 'refresh' method
- if monitor.conf_in_use["skb_mark"] != monitor.conf["skb_mark"]:
- needs_refresh = True
-
- return needs_refresh
-
- def config_tunnel(self, tunnel):
- if tunnel.conf["psk"]:
- self.secrets_file.write('%%any %s : PSK "%s"\n' %
- (tunnel.conf["remote_ip"], tunnel.conf["psk"]))
- auth_section = self.auth_tmpl["psk"].substitute(tunnel.conf)
- elif tunnel.conf["remote_cert"]:
- auth_section = self.auth_tmpl["pki_remote"].substitute(tunnel.conf)
- self._nss_import_cert(tunnel.conf["remote_cert"],
- self.CERT_PREFIX + tunnel.conf["remote_name"],
- 'P,P,P')
- else:
- auth_section = self.auth_tmpl["pki_ca"].substitute(tunnel.conf)
-
- vals = tunnel.conf.copy()
- vals["auth_section"] = auth_section
- vals["version"] = tunnel.version
- conf_text = transp_tmpl[tunnel.conf["tunnel_type"]].substitute(vals)
- self.conf_file.write(conf_text)
-
- def config_fini(self):
- self.secrets_file.close()
- self.conf_file.close()
- self.secrets_file = None
- self.conf_file = None
-
- def clear_tunnel_state(self, tunnel):
- if tunnel.conf["remote_cert"]:
- name = self.CERT_PREFIX + tunnel.conf["remote_name"]
- self._nss_delete_cert(name)
-
- def refresh(self, monitor):
- vlog.info("Refreshing LibreSwan configuration")
- subprocess.call([self.IPSEC, "auto", "--rereadsecrets"])
- tunnels = set(monitor.tunnels.keys())
-
- # Delete old connections
- conns_dict = self.get_active_conns()
- for ifname, conns in conns_dict.iteritems():
- tunnel = monitor.tunnels.get(ifname)
-
- for conn in conns:
- # IPsec "connection" names 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:
- vlog.err("%s does not contain version number")
- continue
- except 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, "auto", "--delete", conn])
- elif ifname in tunnels:
- tunnels.remove(ifname)
-
- # Activate new connections
- for name in tunnels:
- ver = monitor.tunnels[name].version
-
- if monitor.tunnels[name].conf["tunnel_type"] == "gre":
- conn = "%s-%s" % (name, ver)
- self._start_ipsec_connection(conn)
- else:
- conn_in = "%s-in-%s" % (name, ver)
- conn_out = "%s-out-%s" % (name, ver)
- self._start_ipsec_connection(conn_in)
- self._start_ipsec_connection(conn_out)
-
- # Update shunt policy if changed
- if monitor.conf_in_use["skb_mark"] != monitor.conf["skb_mark"]:
- if monitor.conf["skb_mark"]:
- subprocess.call([self.IPSEC, "auto", "--add",
- "--asynchronous", "prevent_unencrypted_gre"])
- subprocess.call([self.IPSEC, "auto", "--add",
- "--asynchronous", "prevent_unencrypted_geneve"])
- subprocess.call([self.IPSEC, "auto", "--add",
- "--asynchronous", "prevent_unencrypted_stt"])
- subprocess.call([self.IPSEC, "auto", "--add",
- "--asynchronous", "prevent_unencrypted_vxlan"])
- else:
- subprocess.call([self.IPSEC, "auto", "--delete",
- "--asynchronous", "prevent_unencrypted_gre"])
- subprocess.call([self.IPSEC, "auto", "--delete",
- "--asynchronous", "prevent_unencrypted_geneve"])
- subprocess.call([self.IPSEC, "auto", "--delete",
- "--asynchronous", "prevent_unencrypted_stt"])
- subprocess.call([self.IPSEC, "auto", "--delete",
- "--asynchronous", "prevent_unencrypted_vxlan"])
- monitor.conf_in_use["skb_mark"] = monitor.conf["skb_mark"]
-
- def get_active_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 LibreSwan connection name as <key> and more detailed
- sample line from the parsed outpus as <value>. """
-
- conns = {}
- proc = subprocess.Popen([self.IPSEC, 'status'], stdout=subprocess.PIPE)
-
- while True:
- line = proc.stdout.readline().strip()
- if line == '':
- break
-
- m = re.search(r"#\d+: \"(.*)\".*", line)
- if not m:
- continue
-
- conn = m.group(1)
- m = re.match(r"(.*)(-in-\d+|-out-\d+|-\d+)", conn)
- if not m:
- continue
-
- ifname = m.group(1)
- if ifname not in conns:
- conns[ifname] = {}
- (conns[ifname])[conn] = line
-
- return conns
-
- def _start_ipsec_connection(self, conn):
- # In a corner case, LibreSwan daemon restarts for some reason and
- # the "ipsec auto --start" command is lost. Just retry to make sure
- # the command is received by LibreSwan.
- while True:
- proc = subprocess.Popen([self.IPSEC, "auto", "--start",
- "--asynchronous", conn],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- perr = str(proc.stderr.read())
- pout = str(proc.stdout.read())
- if not re.match(r".*Connection refused.*", perr) and \
- not re.match(r".*need --listen.*", pout):
- break
-
- def _nss_clear_database(self):
- """Remove all OVS IPsec related state from the NSS database"""
- try:
- proc = subprocess.Popen(['certutil', '-L', '-d',
- 'sql:/etc/ipsec.d/'],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- lines = proc.stdout.readlines()
-
- for line in lines:
- s = line.strip().split()
- if len(s) < 1:
- continue
- name = s[0]
- if name.startswith(self.CERT_PREFIX):
- self._nss_delete_cert(name)
- elif name.startswith(self.CERTKEY_PREFIX):
- self._nss_delete_cert_and_key(name)
-
- except Exception as e:
- vlog.err("Failed to clear NSS database.\n" + str(e))
-
- def _nss_import_cert(self, cert, name, cert_type):
- """Cert_type is 'CT,,' for the CA certificate and 'P,P,P' for the
- normal certificate."""
- try:
- proc = subprocess.Popen(['certutil', '-A', '-a', '-i', cert,
- '-d', 'sql:/etc/ipsec.d/', '-n',
- name, '-t', cert_type],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- proc.wait()
- if proc.returncode:
- raise Exception(proc.stderr.read())
- except Exception as e:
- vlog.err("Failed to import ceretificate into NSS.\n" + str(e))
-
- def _nss_delete_cert(self, name):
- try:
- proc = subprocess.Popen(['certutil', '-D', '-d',
- 'sql:/etc/ipsec.d/', '-n', name],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- proc.wait()
- if proc.returncode:
- raise Exception(proc.stderr.read())
- except Exception as e:
- vlog.err("Failed to delete ceretificate from NSS.\n" + str(e))
-
- def _nss_import_cert_and_key(self, cert, key, name):
- try:
- # Avoid deleting other files
- path = os.path.abspath('/tmp/%s.p12' % name)
- if not path.startswith('/tmp/'):
- raise Exception("Illegal certificate name!")
-
- # Create p12 file from pem files
- proc = subprocess.Popen(['openssl', 'pkcs12', '-export',
- '-in', cert, '-inkey', key, '-out',
- path, '-name', name, '-passout', 'pass:'],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- proc.wait()
- if proc.returncode:
- raise Exception(proc.stderr.read())
-
- # Load p12 file to the database
- proc = subprocess.Popen(['pk12util', '-i', path, '-d',
- 'sql:/etc/ipsec.d/', '-W', ''],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- proc.wait()
- if proc.returncode:
- raise Exception(proc.stderr.read())
-
- except Exception as e:
- vlog.err("Import cert and key failed.\n" + str(e))
- os.remove(path)
-
- def _nss_delete_cert_and_key(self, name):
- try:
- # Delete certificate and private key
- proc = subprocess.Popen(['certutil', '-F', '-d',
- 'sql:/etc/ipsec.d/', '-n', name],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- proc.wait()
- if proc.returncode:
- raise Exception(proc.stderr.read())
-
- except Exception as e:
- vlog.err("Delete cert and key failed.\n" + str(e))
-
-
-class IPsecTunnel(object):
- """This is the base class for IPsec tunnel."""
-
- unixctl_config_tmpl = Template("""\
- Tunnel Type: $tunnel_type
- Remote IP: $remote_ip
- SKB mark: $skb_mark
- Local cert: $certificate
- Local name: $local_name
- Local key: $private_key
- Remote cert: $remote_cert
- Remote name: $remote_name
- CA cert: $ca_cert
- PSK: $psk
-""")
-
- unixctl_status_tmpl = Template("""\
- Ofport: $ofport
- CFM state: $cfm_state
-""")
-
- def __init__(self, name, row):
- self.name = name # 'name' will not change because it is key in OVSDB
- self.version = 0 # 'version' is increased on configuration changes
- self.last_refreshed_version = -1
- self.state = "INIT"
- self.conf = {}
- self.status = {}
- self.update_conf(row)
-
- def update_conf(self, row):
- """This function updates IPsec tunnel configuration by using 'row'
- from OVSDB interface table. If configuration was actually changed
- in OVSDB then this function returns True. Otherwise, it returns
- False."""
- ret = False
- options = row.options
- remote_cert = options.get("remote_cert")
- remote_name = options.get("remote_name")
- if remote_cert:
- remote_name = monitor._get_cn_from_cert(remote_cert)
-
- new_conf = {
- "ifname": self.name,
- "tunnel_type": row.type,
- "remote_ip": options.get("remote_ip"),
- "skb_mark": monitor.conf["skb_mark"],
- "certificate": monitor.conf["pki"]["certificate"],
- "private_key": monitor.conf["pki"]["private_key"],
- "ca_cert": monitor.conf["pki"]["ca_cert"],
- "remote_cert": remote_cert,
- "remote_name": remote_name,
- "local_name": monitor.conf["pki"]["local_name"],
- "psk": options.get("psk")}
-
- if self.conf != new_conf:
- # Configuration was updated in OVSDB. Validate it and figure
- # out what to do next with this IPsec tunnel. Also, increment
- # version number of this IPsec tunnel so that we could tell
- # apart old and new tunnels in "ipsec status" output.
- self.version += 1
- ret = True
- self.conf = new_conf
-
- if self._is_valid_tunnel_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."""
- self.version += 1
- self.state = "REMOVED"
-
- 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 = "IPsec 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 _is_valid_tunnel_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 IKE daemon."""
-
- self.invalid_reason = None
-
- if not self.conf["remote_ip"]:
- self.invalid_reason = ": 'remote_ip' is not set"
- return False
-
- if self.conf["psk"]:
- if self.conf["certificate"] or self.conf["private_key"] \
- or self.conf["ca_cert"] or self.conf["remote_cert"] \
- or self.conf["remote_name"]:
- self.invalid_reason = ": 'certificate', 'private_key', "\
- "'ca_cert', 'remote_cert', and "\
- "'remote_name' must be unset with PSK"
- return False
- # If configuring authentication with CA-signed certificate or
- # self-signed certificate, the 'remote_name' should be specified at
- # this point. When using CA-signed certificate, the 'remote_name' is
- # read from interface's options field. When using self-signed
- # certificate, the 'remote_name' is extracted from the 'remote_cert'
- # file.
- elif self.conf["remote_name"]:
- if not self.conf["certificate"]:
- self.invalid_reason = ": must set 'certificate' as local"\
- " certificate when using CA-signed"\
- " certificate or self-signed"\
- " certificate to authenticate peers"
- return False
- elif not self.conf["private_key"]:
- self.invalid_reason = ": must set 'private_key' as local"\
- " private key when using CA-signed"\
- " certificate or self-signed"\
- " certificate to authenticate peers"
- return False
- if not self.conf["remote_cert"] and not self.conf["ca_cert"]:
- self.invalid_reason = ": must set 'remote_cert' when using"\
- " self-signed certificate"\
- " authentication or 'ca_cert' when"\
- " using CA-signed certificate"\
- " authentication"
- return False
- else:
- self.invalid_reason = ": must choose a authentication method"
- return False
-
- return True
-
-
-class IPsecMonitor(object):
- """This class monitors and configures IPsec tunnels"""
-
- def __init__(self, root_prefix, ike_daemon):
- self.IPSEC = root_prefix + "/usr/sbin/ipsec"
- self.tunnels = {}
-
- # Global configuration shared by all tunnels
- self.conf = {
- "pki": {
- "private_key": None,
- "certificate": None,
- "ca_cert": None,
- "local_name": None
- },
- "skb_mark": None
- }
- self.conf_in_use = copy.deepcopy(self.conf)
-
- # Choose to either use StrongSwan or LibreSwan as IKE daemon
- if ike_daemon == "strongswan":
- self.ike_helper = StrongSwanHelper(root_prefix)
- elif ike_daemon == "libreswan":
- self.ike_helper = LibreSwanHelper(root_prefix)
- else:
- vlog.err("The IKE daemon should be strongswan or libreswan.")
- sys.exit(1)
-
- # Check whether ipsec command is available
- if not os.path.isfile(self.IPSEC) or \
- not os.access(self.IPSEC, os.X_OK):
- vlog.err("IKE daemon is not installed in the system.")
-
- self.ike_helper.restart_ike_daemon()
-
- def is_tunneling_type_supported(self, tunnel_type):
- """Returns True if we know how to configure IPsec for these
- types of tunnels. Otherwise, returns False."""
- return tunnel_type in ["gre", "geneve", "vxlan", "stt"]
-
- def is_ipsec_required(self, options_column):
- """Return True if tunnel needs to be encrypted. Otherwise,
- returns False."""
- return "psk" in options_column or \
- "remote_name" in options_column or \
- "remote_cert" in options_column
-
- def add_tunnel(self, name, row):
- """Adds a new tunnel that monitor will provision with 'name'."""
- vlog.info("Tunnel %s appeared in OVSDB" % (name))
- self.tunnels[name] = IPsecTunnel(name, row)
-
- def update_tunnel(self, name, row):
- """Updates configuration of already existing tunnel with 'name'."""
- tunnel = self.tunnels[name]
- if tunnel.update_conf(row):
- vlog.info("Tunnel's '%s' configuration changed in OVSDB to %u" %
- (tunnel.name, tunnel.version))
-
- def del_tunnel(self, name):
- """Deletes tunnel by 'name'."""
- vlog.info("Tunnel %s disappeared from OVSDB" % (name))
- self.tunnels[name].mark_for_removal()
-
- def update_conf(self, pki, skb_mark):
- """Update the global configuration for IPsec tunnels"""
- self.conf["pki"]["certificate"] = pki[0]
- self.conf["pki"]["private_key"] = pki[1]
- self.conf["pki"]["ca_cert"] = pki[2]
- self.conf["pki"]["local_name"] = pki[3]
-
- # Update skb_mark used in IPsec policies.
- self.conf["skb_mark"] = skb_mark
-
- def read_ovsdb_open_vswitch_table(self, data):
- """This functions reads IPsec relevant configuration from Open_vSwitch
- table."""
- pki = [None, None, None, None]
- skb_mark = None
- is_valid = False
-
- for row in data["Open_vSwitch"].rows.itervalues():
- pki[0] = row.other_config.get("certificate")
- pki[1] = row.other_config.get("private_key")
- pki[2] = row.other_config.get("ca_cert")
- skb_mark = row.other_config.get("ipsec_skb_mark")
-
- # Test whether it's a valid configration
- if pki[0] and pki[1]:
- pki[3] = self._get_cn_from_cert(pki[0])
- if pki[3]:
- is_valid = True
- elif not pki[0] and not pki[1] and not pki[2]:
- is_valid = True
-
- if not is_valid:
- vlog.warn("The cert and key configuration is not valid. "
- "The valid configuations are 1): certificate, private_key "
- "and ca_cert are not set; or 2): certificate and "
- "private_key are all set.")
- else:
- self.update_conf(pki, skb_mark)
-
- def read_ovsdb_interface_table(self, data):
- """This function reads the IPsec relevant configuration from Interface
- table."""
- ifaces = set()
-
- for row in data["Interface"].rows.itervalues():
- if not self.is_tunneling_type_supported(row.type):
- continue
- if not self.is_ipsec_required(row.options):
- continue
- if row.name in self.tunnels:
- self.update_tunnel(row.name, row)
- else:
- self.add_tunnel(row.name, row)
- ifaces.add(row.name)
-
- # Mark for removal those tunnels that just disappeared from OVSDB
- for tunnel in self.tunnels.keys():
- if tunnel not in ifaces:
- self.del_tunnel(tunnel)
-
- def read_ovsdb(self, data):
- """This function reads all configuration from OVSDB that
- ovs-monitor-ipsec is interested in."""
- self.read_ovsdb_open_vswitch_table(data)
- self.read_ovsdb_interface_table(data)
-
- 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 the IKE deamon."""
- if not self.tunnels:
- unix_conn.reply("No tunnels configured with IPsec")
- return
- s = ""
- conns = self.ike_helper.get_active_conns()
- for name, tunnel in self.tunnels.iteritems():
- s += tunnel.show(policies, securities, conns)
- unix_conn.reply(s)
-
- def run(self):
- """This function runs state machine that represents whole
- IPsec configuration (i.e. merged together from individual
- tunnel state machines). It creates configuration files and
- tells IKE daemon to update configuration."""
- needs_refresh = False
- removed_tunnels = []
-
- self.ike_helper.config_init()
-
- if self.ike_helper.config_global(self):
- needs_refresh = True
-
- for name, tunnel in self.tunnels.iteritems():
- if tunnel.last_refreshed_version != tunnel.version:
- tunnel.last_refreshed_version = tunnel.version
- needs_refresh = True
-
- if tunnel.state == "REMOVED" or tunnel.state == "INVALID":
- removed_tunnels.append(name)
- elif tunnel.state == "CONFIGURED":
- self.ike_helper.config_tunnel(self.tunnels[name])
-
- self.ike_helper.config_fini()
-
- for name in removed_tunnels:
- # LibreSwan needs to clear state from database
- if hasattr(self.ike_helper, "clear_tunnel_state"):
- self.ike_helper.clear_tunnel_state(self.tunnels[name])
- del self.tunnels[name]
-
- if needs_refresh:
- self.ike_helper.refresh(self)
-
- def _get_cn_from_cert(self, cert):
- try:
- proc = subprocess.Popen(['openssl', 'x509', '-noout', '-subject',
- '-nameopt', 'RFC2253', '-in', cert],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- proc.wait()
- if proc.returncode:
- raise Exception(proc.stderr.read())
- m = re.search(r"CN=(.+?),", proc.stdout.readline())
- if not m:
- raise Exception("No CN in the certificate subject.")
- except Exception as e:
- vlog.warn(str(e))
- return None
-
- return m.group(1)
-
-
-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 monitor
- conns = monitor.ike_helper.get_active_conns()
- conn.reply(str(conns))
-
-
-def unixctl_show(conn, unused_argv, unused_aux):
- global monitor
- global xfrm
- policies = xfrm.get_policies()
- securities = xfrm.get_securities()
- monitor.show(conn, policies, securities)
-
-
-def unixctl_refresh(conn, unused_argv, unused_aux):
- global monitor
- monitor.ike_helper.refresh(monitor)
- conn.reply(None)
-
-
-def unixctl_exit(conn, unused_argv, unused_aux):
- global monitor
- global exiting
- exiting = True
-
- # Make sure persistent global states are cleared
- monitor.update_conf([None, None, None, None], None)
- # Make sure persistent tunnel states are cleared
- for tunnel in monitor.tunnels.keys():
- monitor.del_tunnel(tunnel)
- monitor.run()
-
- conn.reply(None)
-
-
-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).")
- parser.add_argument("--ike-daemon", metavar="IKE-DAEMON",
- help="The IKE daemon used for IPsec tunnels"
- " (either libreswan or strongswan).")
-
- 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 monitor
- global xfrm
-
- root_prefix = args.root_prefix if args.root_prefix else ""
- xfrm = XFRM(root_prefix)
- monitor = IPsecMonitor(root_prefix, args.ike_daemon)
-
- remote = args.database
- schema_helper = ovs.db.idl.SchemaHelper()
- schema_helper.register_columns("Interface",
- ["name", "type", "options", "cfm_fault",
- "ofport"])
- schema_helper.register_columns("Open_vSwitch", ["other_config"])
- 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("refresh", "", 0, 0, unixctl_refresh, 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)
-
- # Sequence number when OVSDB was processed last time
- seqno = idl.change_seqno
-
- while True:
- unixctl_server.run()
- if exiting:
- break
-
- idl.run()
- if seqno != idl.change_seqno:
- monitor.read_ovsdb(idl.tables)
- seqno = idl.change_seqno
-
- monitor.run()
-
- poller = ovs.poller.Poller()
- unixctl_server.wait(poller)
- idl.wait(poller)
- poller.block()
-
- unixctl_server.close()
- idl.close()
-
-
-if __name__ == '__main__':
- try:
- main()
- except SystemExit:
- # Let system.exit() calls complete normally
- raise
- except:
- vlog.exception("traceback")
- sys.exit(ovs.daemon.RESTART_EXIT_CODE)
--
2.26.2
More information about the dev
mailing list