Message ID | 20220322140838.28772-2-ehakim@nvidia.com |
---|---|
State | Changes Requested |
Headers | show |
Series | ovs-monitor-ipsec: move to using swanctl.conf when using strongswan as IKE daemon | expand |
Context | Check | Description |
---|---|---|
ovsrobot/apply-robot | success | apply and check: success |
ovsrobot/github-robot-_Build_and_Test | success | github build: passed |
ovsrobot/intel-ovs-compilation | success | test: success |
On Tue, Mar 22, 2022 at 10:09 AM Emeel Hakim via dev <ovs-dev@openvswitch.org> wrote: > > 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 <raeds@nvidia.com> > Signed-off-by: Emeel Hakim <ehakim@nvidia.com> > --- > ipsec/ovs-monitor-ipsec.in | 466 ++++++++++++++++++++++++++----------- > 1 file changed, 325 insertions(+), 141 deletions(-) > > diff --git a/ipsec/ovs-monitor-ipsec.in b/ipsec/ovs-monitor-ipsec.in > index c9f3cc5a1..8c72563e1 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,45 +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") > - proc = subprocess.Popen([self.IPSEC, "update"], > - stdout=subprocess.PIPE, > - stderr=subprocess.PIPE) > - outs, errs = proc.communicate() > - if proc.returncode != 0: > - vlog.err("StrongSwan failed to update configuration:\n" > - "%s \n %s" % (str(outs), str(errs))) > - > - 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) > @@ -378,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): > @@ -449,6 +585,53 @@ conn prevent_unencrypted_vxlan > leftrsasigkey=%cert > rightca=%same""")} > The following templates actually introduce a syntax error for me. I had to make the following sort of change to get ipsec working again: - conn $ifname-$version +conn $ifname-$version $auth_section - leftprotoport=gre - rightprotoport=gre + leftprotoport=gre + rightprotoport=gre WIthout that, pluto doesn't seem to parse the config file properly. Cheers, M > + 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_" > > @@ -553,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 > dev@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev >
diff --git a/ipsec/ovs-monitor-ipsec.in b/ipsec/ovs-monitor-ipsec.in index c9f3cc5a1..8c72563e1 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,45 +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") - proc = subprocess.Popen([self.IPSEC, "update"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - outs, errs = proc.communicate() - if proc.returncode != 0: - vlog.err("StrongSwan failed to update configuration:\n" - "%s \n %s" % (str(outs), str(errs))) - - 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) @@ -378,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): @@ -449,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_" @@ -553,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):