On Tue, Mar 22, 2022 at 10:09 AM Emeel Hakim via dev
<[email protected]> 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 <[email protected]>
> Signed-off-by: Emeel Hakim <[email protected]>
> ---
> 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
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev