As strongswan moved to the modern vici-based interface,this patch modifies ovs-monitor-ipsec to use strongswan's vici-based configuration instead of the legacy stroke-based configuration.
Reviewed-by: Raed Salem <ra...@nvidia.com> Signed-off-by: Emeel Hakim <eha...@nvidia.com> --- ipsec/ovs-monitor-ipsec.in | 459 ++++++++++++++++++++++++++----------- 1 file changed, 325 insertions(+), 134 deletions(-) diff --git a/ipsec/ovs-monitor-ipsec.in b/ipsec/ovs-monitor-ipsec.in index a8b0705d9..582c96132 100755 --- a/ipsec/ovs-monitor-ipsec.in +++ b/ipsec/ovs-monitor-ipsec.in @@ -32,52 +32,6 @@ 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 @@ -160,72 +114,249 @@ charon { } """ % (FILE_HEADER) - CONF_HEADER = """%s -config setup - uniqueids=yes + SWANCTL_CONF_HEADER = """%s +conn-defaults { + unique = replace + reauth_time = 0 + version = 2 + proposals = aes128-sha256-x25519 +} -conn %%default - keyingtries=%%forever - type=transport - keyexchange=ikev2 - auto=route - ike=aes256gcm16-sha256-modp2048 - esp=aes256gcm16-modp2048 +child-defaults { + esp_proposals = aes256gcm16-modp2048-esn + mode = transport + policies_fwd_out = yes + start_action = start +} """ % (FILE_HEADER) - CA_SECTION = """ca ca_auth - cacert=%s + CA_SECTION = """authorities { + ca_auth { + cacert=%s + } +} """ - SHUNT_POLICY = """conn prevent_unencrypted_gre - type=drop - leftprotoport=gre - mark={0} + SHUNT_POLICY = """connections {{ + shunts {{ + children {{ + prevent_unencrypted_gre {{ + local_ts = 0.0.0.0/0 [gre] + mark_in = {0} + mark_out = {0} + mode = drop + start_action = trap + }} + prevent_unencrypted_gre_ipv6 {{ + local_ts = ::/0 [gre] + mark_in = {0} + mark_out = {0} + mode = drop + start_action = trap + }} + prevent_unencrypted_geneve {{ + local_ts = 0.0.0.0/0 [udp/6081] + mark_in = {0} + mark_out = {0} + mode = drop + start_action = trap + }} + prevent_unencrypted_geneve_ipv6 {{ + local_ts = ::/0 [udp/6081] + mark_in = {0} + mark_out = {0} + mode = drop + start_action = trap + }} + prevent_unencrypted_stt {{ + local_ts = 0.0.0.0/0 [tcp/7471] + mark_in = {0} + mark_out = {0} + mode = drop + start_action = trap + }} + prevent_unencrypted_stt_ipv6 {{ + local_ts = ::/0 [tcp/7471] + mark_in = {0} + mark_out = {0} + mode = drop + start_action = trap + }} + prevent_unencrypted_vxlan {{ + local_ts = 0.0.0.0/0 [udp/4789] + mark_in = {0} + mark_out = {0} + mode = drop + start_action = trap + }} + prevent_unencrypted_vxlan_ipv6 {{ + local_ts = ::/0 [udp/4789] + mark_in = {0} + mark_out = {0} + mode = drop + start_action = trap + }} + }} + }} +}} +""" + auth_tmpl = {"psk": Template("""\ +local { + auth = psk + id = $local_ip + } + remote { + auth = psk + id = $remote_ip + }"""), + "pki_remote": Template("""\ +local { + auth = pubkey + id = $local_name + certs = $certificate + } + remote { + auth = pubkey + id = $remote_name + certs = $remote_cert + }"""), + "pki_ca": Template("""\ +local { + auth = pubkey + id = $local_name + certs = $certificate + } + remote { + auth = pubkey + id = $remote_name + }""")} + + SECRETS_SECTION = """secrets { + ike-$ifname { + id = $local_ip + secret = $psk + } +} -conn prevent_unencrypted_geneve - type=drop - leftprotoport=udp/6081 - mark={0} +""" + transp_tmpl = {"gre": Template("""\ +connections { + $ifname-$version : conn-defaults{ + local_addrs = $local_addrs + remote_addrs = $remote_ip + + $auth_section + + children { + $ifname-$version : child-defaults { + local_ts = $local_ip/$subnet [gre] + remote_ts = $remote_ip/$subnet [gre] + } + } + } +} -conn prevent_unencrypted_stt - type=drop - leftprotoport=tcp/7471 - mark={0} +"""), "gre64": Template("""\ +connections { + $ifname-$version : conn-defaults{ + local_addrs = $local_addrs + remote_addrs = $remote_ip + + $auth_section + + children { + $ifname-$version : child-defaults { + local_ts = $local_ip/$subnet [gre] + remote_ts = $remote_ip/$subnet [gre] + } + } + } +} -conn prevent_unencrypted_vxlan - type=drop - leftprotoport=udp/4789 - mark={0} +"""), "geneve": Template("""\ +connections { + $ifname-$version : conn-defaults{ + local_addrs = $local_addrs + remote_addrs = $remote_ip + + $auth_section + + children { + $ifname-in-$version : child-defaults { + local_ts = $local_ip/$subnet [udp/6081] + remote_ts = $remote_ip/$subnet [udp] + } + $ifname-out-$version : child-defaults { + local_ts = $local_ip/$subnet [udp] + remote_ts = $remote_ip/$subnet [udp/6081] + } + } -""" + } +} - auth_tmpl = {"psk": Template("""\ - left=%any - right=$remote_ip - authby=psk"""), - "pki_remote": Template("""\ - left=%any - right=$remote_ip - leftid=$local_name - rightid=$remote_name - leftcert=$certificate - rightcert=$remote_cert"""), - "pki_ca": Template("""\ - left=%any - right=$remote_ip - leftid=$local_name - rightid=$remote_name - leftcert=$certificate""")} +"""), "stt": Template("""\ +connections { + $ifname-$version : conn-defaults{ + local_addrs = $local_addrs + remote_addrs = $remote_ip + + $auth_section + + children { + $ifname-in-$version : child-defaults { + local_ts = $local_ip/$subnet [tcp/7471] + remote_ts = $remote_ip/$subnet [tcp] + } + $ifname-out-$version : child-defaults { + local_ts = $local_ip/$subnet [tcp] + remote_ts = $remote_ip/$subnet [tcp/7471] + } + } + } +} + +"""), "vxlan": Template("""\ +connections { + $ifname-$version : conn-defaults{ + local_addrs = $local_addrs + remote_addrs = $remote_ip + + $auth_section + + children { + $ifname-in-$version : child-defaults { + local_ts = $local_ip/$subnet [udp/4789] + remote_ts = $remote_ip/$subnet [udp] + } + $ifname-out-$version : child-defaults { + local_ts = $local_ip/$subnet [udp] + remote_ts = $remote_ip/$subnet [udp/4789] + } + } + } +} + +""")} 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" + if os.path.exists(root_prefix + "/etc/strongswan.d/"): + self.CHARON_CONF = root_prefix + "/etc/strongswan.d/ovs.conf" + else: + self.CHARON_CONF = (root_prefix + + "/etc/strongswan/strongswan.d/ovs.conf") + if os.path.exists(root_prefix + "/etc/swanctl/conf.d"): + self.SWANCTL_CONF = (root_prefix + + "/etc/swanctl/conf.d/ovs-swanctl.conf") + else: + self.SWANCTL_CONF = (root_prefix + + "/etc/strongswan/swanctl/conf.d/" + + "ovs-swanctl.conf") + self.SYSTEMCTL = root_prefix + "/usr/bin/systemctl" + self.SWANCTL = root_prefix + "/usr/sbin/swanctl" self.conf_file = None - self.secrets_file = None def restart_ike_daemon(self): """This function restarts StrongSwan.""" @@ -233,26 +364,24 @@ conn prevent_unencrypted_vxlan 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 = open(self.SWANCTL_CONF, "w") + f.write(self.SWANCTL_CONF_HEADER) f.close() vlog.info("Restarting StrongSwan") - subprocess.call([self.IPSEC, "restart"]) + subprocess.call((self.SYSTEMCTL + + " restart strongswan-starter.service").split()) def get_active_conns(self): - """This function parses output from 'ipsec status' command. + """This function parses output from 'swanctl --list-conns' 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) + proc = subprocess.Popen([self.SWANCTL, '--list-conns'], + stdout=subprocess.PIPE) while True: line = proc.stdout.readline().strip().decode() @@ -272,10 +401,8 @@ conn prevent_unencrypted_vxlan 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) + self.conf_file = open(self.SWANCTL_CONF, "w") + self.conf_file.write(self.SWANCTL_CONF_HEADER) def config_global(self, monitor): """Configure the global state of IPsec tunnels.""" @@ -299,13 +426,10 @@ conn prevent_unencrypted_vxlan 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) + secrets = Template(self.SECRETS_SECTION).substitute(tunnel.conf) else: - self.secrets_file.write("%%any %s : RSA %s\n" % - (tunnel.conf["remote_ip"], - tunnel.conf["private_key"])) + secrets = None if tunnel.conf["remote_cert"]: tmpl = self.auth_tmpl["pki_remote"] auth_section = tmpl.substitute(tunnel.conf) @@ -316,38 +440,43 @@ conn prevent_unencrypted_vxlan vals = tunnel.conf.copy() vals["auth_section"] = auth_section vals["version"] = tunnel.version - conf_text = transp_tmpl[tunnel.conf["tunnel_type"]].substitute(vals) + if tunnel.conf["address_family"] == "IPv6": + vals["local_addrs"] = "::/0" + vals["subnet"] = "64" + else: + vals["local_addrs"] = "0.0.0.0/0" + vals["subnet"] = "32" + if vals["local_ip"] == "%defaultroute": + if tunnel.conf["address_family"] == "IPv6": + vals["local_ip"] = "::/0" + else: + vals["local_ip"] = "0.0.0.0/0" + conf_text = self.transp_tmpl[tunnel.conf[ + "tunnel_type"]].substitute(vals) self.conf_file.write(conf_text) + if secrets is not None: + self.conf_file.write(secrets) + 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 + 1. once "swanctl --load-all" command that tells strongSwan to load + all new tunnels from "swanctl.conf"; and + 2. for every removed tunnel "swanctl -t --child <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>" + # "swanctl --load-all" command does not remove those tunnels that were + # updated or that disappeared from the swanctl.conf files. So, we have + # to manually remove them by calling "swanctl -t --child <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.items(): tunnel = monitor.tunnels.get(ifname) @@ -371,7 +500,21 @@ conn prevent_unencrypted_vxlan if not tunnel or tunnel.version != ver: vlog.info("%s is outdated %u" % (conn, ver)) - subprocess.call([self.IPSEC, "stroke", "down-nb", conn]) + self.terminate_ipsec_connection(conn) + + self.update_ipsec_connections() + + def update_ipsec_connections(self): + process = subprocess.Popen((self.SWANCTL + " --load-all").split(), + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + err = str(process.stderr.read()) + if re.match(r".*Error.*", err, re.IGNORECASE) is not None: + vlog.err(err) + + def terminate_ipsec_connection(self, conn_name): + subprocess.Popen((self.SWANCTL + " -t --child " + + conn_name).split(), stdout=subprocess.PIPE) + vlog.info("IPsec connection terminated for " + conn_name) class LibreSwanHelper(object): @@ -442,6 +585,53 @@ conn prevent_unencrypted_vxlan leftrsasigkey=%cert rightca=%same""")} + 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 + + """)} + CERT_PREFIX = "ovs_cert_" CERTKEY_PREFIX = "ovs_certkey_" @@ -546,7 +736,8 @@ conn prevent_unencrypted_vxlan vals = tunnel.conf.copy() vals["auth_section"] = auth_section vals["version"] = tunnel.version - conf_text = transp_tmpl[tunnel.conf["tunnel_type"]].substitute(vals) + conf_text = self.transp_tmpl[tunnel.conf[ + "tunnel_type"]].substitute(vals) self.conf_file.write(conf_text) def config_fini(self): -- 2.21.3 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev