Skip to content

Commit

Permalink
ipsec: Add support for using non-root ipsec.conf.
Browse files Browse the repository at this point in the history
Typical configuration file hierarchy for Libreswan in distributions
looks like this:

  /etc
    /ipsec.conf
    /ipsec.d
      /*.conf
    /crypto-policies/back-ends/libreswan.config

The root ipsec.conf contains the 'setup' section with the base
configuration of the IKE daemon, includes system-wide crypto-policies
and all the sub-config files in ipsec.d folder describing connections.

ovs-monitor-ipsec today is not able to leverage this structure, because
it requires the complete ownership of the ipsec.conf.  If someone
attempts to pass a sub-config file to the daemon in order to make it
not overwrite the root ipsec.conf, this may cause a lot of trouble:

 1. New tunnel is created in OVS.
 2. ovs-monitor-ipsec writes it into sub-config file.
 3. ovs-monitor-ipsec calls ipsec --start conn --config sub-config
 4. Libreswan starts connection using configuration from only the
    sub-config and not taking into account any other file.
 5. Re-start Libreswan.
 6. Libreswan now reads all the files and configures connections
    using information from all the configuration files, including
    system-wide crypto policies and other potential 'conn %default'
    sections from all the files.
 7. Now the connection is configured differently and potentially
    in an incompatible way with the other side.

Worst of all is the behavior is unpredictable, taking into account
the re-start can happen due to a crash or other random event.

Another point is that 'setup' and 'conn %default' sections defined
in our sub-config file will also bleed out configuration to connections
defined in other files.  And it's hard to say in which order
configuration will be applied, because it's not clear in which order
the files are included and parsed.

So, this kind of file structure cannot be safely used.

Let's add a minimal support for running with a sub-config.  A new
option '--root-ipsec-conf' is introduced to specify the location of
the root ipsec.conf file, so ovs-monitor-ipsec can provide it while
calling ipec commands instead.  This will make Libreswan (pluto) to
parse the whole tree of includes and apply the same configuration
every time, regardless of restarts and other issues.

When this new option is set, ovs-monitor-ipsec will also not define
the 'setup' section to avoid overriding global configuration and will
not define 'conn %default' section for the same reason.  Instead,
important connection options will be defined for every connection,
so they are still applied without polluting defaults.

The 'setup' section is just omitted in this case.  We only define
'uniqeids', but it's true by default and we may assume users know
what are they doing if they are changing this config in the main
ipsec.conf.  The Libreswan documentation also discourages from
turning this option off and mentions that it may be removed in the
future.

Only implementing for Libreswan, because we do not even support
non-default location of ipsec.conf with StrongSwan today.

Acked-by: Mike Pattrick <[email protected]>
Signed-off-by: Ilya Maximets <[email protected]>
  • Loading branch information
igsilya committed Dec 16, 2024
1 parent 1be33d5 commit 09d7c5a
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 24 deletions.
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ Post-v3.4.0
- Tunnels:
* LISP and STT tunnel port types are deprecated and will be removed in
the next release.
- IPsec:
* New option '--root-ipsec-conf' for ovs-monitor-ipsec with Libreswan
to allow cases where '--ipsec-conf' is not the main ipsec.conf, but
included from it. The value should be the path to the main ipsec.conf.


v3.4.0 - 15 Aug 2024
Expand Down
67 changes: 48 additions & 19 deletions ipsec/ovs-monitor-ipsec.in
Original file line number Diff line number Diff line change
Expand Up @@ -425,19 +425,24 @@ conn prevent_unencrypted_vxlan

class LibreSwanHelper(object):
"""This class does LibreSwan specific configurations."""
CONF_HEADER = """%s
CONF_DEFAULT_HEADER = """\
config setup
uniqueids=yes

conn %%default
keyingtries=%%forever
conn %default
"""

CONN_CONF_BASE = """\
keyingtries=%forever
type=transport
auto=route
"""

CONN_CONF_CRYPTO = """\
ike=aes_gcm256-sha2_256
esp=aes_gcm256
ikev2=insist

""" % (FILE_HEADER)
"""

SHUNT_POLICY = """conn prevent_unencrypted_gre
type=drop
Expand Down Expand Up @@ -524,15 +529,20 @@ conn prevent_unencrypted_vxlan
else "/run/pluto/pluto.ctl")

self.IPSEC_CONF = libreswan_root_prefix + ipsec_conf
self.ROOT_IPSEC_CONF = self.IPSEC_CONF
if args.root_ipsec_conf:
self.ROOT_IPSEC_CONF = args.root_ipsec_conf
self.IPSEC_SECRETS = libreswan_root_prefix + ipsec_secrets
self.IPSEC_D = "sql:" + libreswan_root_prefix + ipsec_d
self.IPSEC_CTL = libreswan_root_prefix + ipsec_ctl
self.conf_file = None
self.conns_not_active = set()
self.last_refresh = time.time()
self.secrets_file = None
self.use_default_conn = self.IPSEC_CONF == self.ROOT_IPSEC_CONF
vlog.dbg("Using: " + self.IPSEC)
vlog.dbg("Configuration file: " + self.IPSEC_CONF)
vlog.dbg("Root configuration file: " + self.ROOT_IPSEC_CONF)
vlog.dbg("Secrets file: " + self.IPSEC_SECRETS)
vlog.dbg("ipsec.d: " + self.IPSEC_D)
vlog.dbg("Pluto socket: " + self.IPSEC_CTL)
Expand All @@ -543,7 +553,12 @@ conn prevent_unencrypted_vxlan
self._nss_clear_database()

f = open(self.IPSEC_CONF, "w")
f.write(self.CONF_HEADER)
f.write(FILE_HEADER)
if self.use_default_conn:
f.write(self.CONF_DEFAULT_HEADER)
f.write(self.CONN_CONF_BASE)
f.write(self.CONN_CONF_CRYPTO)
f.write("\n")
f.close()

f = open(self.IPSEC_SECRETS, "w")
Expand All @@ -556,7 +571,13 @@ conn prevent_unencrypted_vxlan
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.conf_file.write(FILE_HEADER)
if self.use_default_conn:
self.conf_file.write(self.CONF_DEFAULT_HEADER)
self.conf_file.write(self.CONN_CONF_BASE)
self.conf_file.write(self.CONN_CONF_CRYPTO)
self.conf_file.write("\n")

self.secrets_file.write(FILE_HEADER)

def config_global(self, monitor):
Expand Down Expand Up @@ -614,6 +635,10 @@ conn prevent_unencrypted_vxlan
if tunnel.conf["address_family"] == "IPv6":
auth_section = self.IPV6_CONN + auth_section

if not self.use_default_conn:
auth_section = self.CONN_CONF_BASE + auth_section
auth_section = self.CONN_CONF_CRYPTO + auth_section

if "custom_options" in tunnel.conf:
for key, value in tunnel.conf["custom_options"].items():
auth_section += "\n " + key + "=" + value
Expand All @@ -638,7 +663,7 @@ conn prevent_unencrypted_vxlan
def refresh(self, monitor):
vlog.info("Refreshing LibreSwan configuration")
run_command(self.IPSEC_AUTO + ["--ctlsocket", self.IPSEC_CTL,
"--config", self.IPSEC_CONF,
"--config", self.ROOT_IPSEC_CONF,
"--rereadsecrets"],
"re-read secrets")

Expand Down Expand Up @@ -708,43 +733,43 @@ conn prevent_unencrypted_vxlan
if monitor.conf_in_use["skb_mark"] != monitor.conf["skb_mark"]:
if monitor.conf["skb_mark"]:
run_command(self.IPSEC_AUTO +
["--config", self.IPSEC_CONF,
["--config", self.ROOT_IPSEC_CONF,
"--ctlsocket", self.IPSEC_CTL,
"--add",
"--asynchronous", "prevent_unencrypted_gre"])
run_command(self.IPSEC_AUTO +
["--config", self.IPSEC_CONF,
["--config", self.ROOT_IPSEC_CONF,
"--ctlsocket", self.IPSEC_CTL,
"--add",
"--asynchronous", "prevent_unencrypted_geneve"])
run_command(self.IPSEC_AUTO +
["--config", self.IPSEC_CONF,
["--config", self.ROOT_IPSEC_CONF,
"--ctlsocket", self.IPSEC_CTL,
"--add",
"--asynchronous", "prevent_unencrypted_stt"])
run_command(self.IPSEC_AUTO +
["--config", self.IPSEC_CONF,
["--config", self.ROOT_IPSEC_CONF,
"--ctlsocket", self.IPSEC_CTL,
"--add",
"--asynchronous", "prevent_unencrypted_vxlan"])
else:
run_command(self.IPSEC_AUTO +
["--config", self.IPSEC_CONF,
["--config", self.ROOT_IPSEC_CONF,
"--ctlsocket", self.IPSEC_CTL,
"--delete",
"--asynchronous", "prevent_unencrypted_gre"])
run_command(self.IPSEC_AUTO +
["--config", self.IPSEC_CONF,
["--config", self.ROOT_IPSEC_CONF,
"--ctlsocket", self.IPSEC_CTL,
"--delete",
"--asynchronous", "prevent_unencrypted_geneve"])
run_command(self.IPSEC_AUTO +
["--config", self.IPSEC_CONF,
["--config", self.ROOT_IPSEC_CONF,
"--ctlsocket", self.IPSEC_CTL,
"--delete",
"--asynchronous", "prevent_unencrypted_stt"])
run_command(self.IPSEC_AUTO +
["--config", self.IPSEC_CONF,
["--config", self.ROOT_IPSEC_CONF,
"--ctlsocket", self.IPSEC_CTL,
"--delete",
"--asynchronous", "prevent_unencrypted_vxlan"])
Expand Down Expand Up @@ -838,13 +863,13 @@ conn prevent_unencrypted_vxlan
self.conns_not_active.discard(conn)
run_command(self.IPSEC_AUTO +
["--ctlsocket", self.IPSEC_CTL,
"--config", self.IPSEC_CONF,
"--config", self.ROOT_IPSEC_CONF,
"--delete", conn], "delete %s" % conn)

def _start_ipsec_connection(self, conn, action):
asynchronous = [] if action == "add" else ["--asynchronous"]
ret, pout, perr = run_command(self.IPSEC_AUTO +
["--config", self.IPSEC_CONF,
["--config", self.ROOT_IPSEC_CONF,
"--ctlsocket", self.IPSEC_CTL,
"--" + action,
*asynchronous, conn],
Expand Down Expand Up @@ -1388,7 +1413,11 @@ def main():
help="Don't restart the IKE daemon on startup.")
parser.add_argument("--ipsec-conf", metavar="IPSEC-CONF",
help="Use DIR/IPSEC-CONF as location for "
" ipsec.conf (libreswan only).")
" ipsec.conf to overwrite (libreswan only).")
parser.add_argument("--root-ipsec-conf", metavar="ROOT-IPSEC-CONF",
help="The read-only root configuration file that"
" 'include's the one provided in --ipsec-conf. Will"
" be used to call ipsec commands (libreswan only).")
parser.add_argument("--ipsec-d", metavar="IPSEC-D",
help="Use DIR/IPSEC-D as location for "
" ipsec.d (libreswan only).")
Expand Down
58 changes: 53 additions & 5 deletions tests/system-ipsec.at
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ m4_define([START_PLUTO], [
--rundir $ovs_base/$1], [0], [], [stderr])
])

dnl IPSEC_ADD_NODE([namespace], [device], [address], [peer address]))
dnl IPSEC_ADD_NODE([namespace], [device], [address], [peer address],
dnl [custom-ipsec-conf])
dnl
dnl Creates a dummy host that acts as an IPsec endpoint. Creates host in
dnl 'namespace' and attaches a veth 'device' to 'namespace' to act as the host
dnl NIC. Assigns 'address' to 'device' and adds the other end of veth 'device' to
dnl 'br0' which is an OVS bridge in the default namespace acting as an underlay
dnl switch. Sets the default gateway of 'namespace' to 'peer address'.
dnl
dnl Starts all daemons in 'namespace' that are required for IPsec
dnl Starts all daemons in 'namespace' that are required for IPsec.
dnl
dnl If 'custom-ipsec-conf' is provided, then it will be used as --ipsec-conf
dnl and the ipsec.conf will be used as --root-ipsec-conf.
m4_define([IPSEC_ADD_NODE],
[ADD_NAMESPACES($1)
dnl Disable DAD. We know we wont get duplicates on this underlay network.
Expand Down Expand Up @@ -56,14 +60,22 @@ m4_define([IPSEC_ADD_NODE],
[0], [], [stderr])
on_exit "kill_ovs_vswitchd `cat $ovs_base/$1/vswitchd.pid`"

m4_if([$5], [], [], [
AT_CHECK([echo "## A read-only root config. ##" > $ovs_base/$1/ipsec.conf])
AT_CHECK([echo "include $ovs_base/$1/$5" >> $ovs_base/$1/ipsec.conf])
])
AT_CHECK

dnl Start pluto
START_PLUTO([$1])
on_exit 'kill $(cat $ovs_base/$1/pluto.pid)'

dnl Start ovs-monitor-ipsec
NS_CHECK_EXEC([$1], [ovs-monitor-ipsec unix:${OVS_RUNDIR}/$1/db.sock\
--pidfile=${OVS_RUNDIR}/$1/ovs-monitor-ipsec.pid --ike-daemon=libreswan\
--ipsec-conf=$ovs_base/$1/ipsec.conf --ipsec-d=$ovs_base/$1/ipsec.d \
--ipsec-conf=$ovs_base/$1/m4_if([$5], [], [ipsec.conf], [$5]) \
m4_if([$5], [], [], [--root-ipsec-conf=$ovs_base/$1/ipsec.conf]) \
--ipsec-d=$ovs_base/$1/ipsec.d \
--ipsec-secrets=$ovs_base/$1/secrets \
--log-file=$ovs_base/$1/ovs-monitor-ipsec.log \
--ipsec-ctl=$ovs_base/$1/pluto.ctl \
Expand All @@ -75,8 +87,10 @@ m4_define([IPSEC_ADD_NODE],
[ovs-vsctl --db unix:$ovs_base/$1/db.sock add-br br-ipsec \
-- set-controller br-ipsec punix:$ovs_base/br-ipsec.$1.mgmt])]
)
m4_define([IPSEC_ADD_NODE_LEFT], [IPSEC_ADD_NODE(left, p0, $1, $2)])
m4_define([IPSEC_ADD_NODE_RIGHT], [IPSEC_ADD_NODE(right, p1, $1, $2)])
m4_define([IPSEC_ADD_NODE_LEFT],
[IPSEC_ADD_NODE(left, p0, $1, $2, [$3])])
m4_define([IPSEC_ADD_NODE_RIGHT],
[IPSEC_ADD_NODE(right, p1, $1, $2, [$3])])

dnl OVS_VSCTL([namespace], [sub-command])
dnl
Expand Down Expand Up @@ -411,6 +425,40 @@ CHECK_ESP_TRAFFIC
OVS_TRAFFIC_VSWITCHD_STOP()
AT_CLEANUP

AT_SETUP([IPsec -- Libreswan (ipv4, geneve, custom conf)])
AT_KEYWORDS([ipsec libreswan ipv4 geneve psk custom conf])
dnl Note: Geneve test may not work on older kernels due to CVE-2020-25645
dnl https://bugzilla.redhat.com/show_bug.cgi?id=1883988

CHECK_LIBRESWAN()
OVS_TRAFFIC_VSWITCHD_START()
IPSEC_SETUP_UNDERLAY()

dnl Set up hosts.
IPSEC_ADD_NODE_LEFT(10.1.1.1, 10.1.1.2, [custom.conf])
IPSEC_ADD_NODE_RIGHT(10.1.1.2, 10.1.1.1, [custom.conf])

dnl Set up IPsec tunnel on 'left' host.
IPSEC_ADD_TUNNEL_LEFT([geneve],
[options:remote_ip=10.1.1.2 options:psk=swordfish])

dnl Set up IPsec tunnel on 'right' host.
IPSEC_ADD_TUNNEL_RIGHT([geneve],
[options:remote_ip=10.1.1.1 options:psk=swordfish])
CHECK_ESP_TRAFFIC

dnl Check that custom.conf doesn't include default section, but has
dnl ike and esp configuration per connection.
AT_CHECK([grep -q "conn %default" $ovs_base/left/custom.conf], [1])
AT_CHECK([grep -c -E "(ike|ikev2|esp)=" $ovs_base/left/custom.conf], [0], [6
])
AT_CHECK([grep -q "conn %default" $ovs_base/right/custom.conf], [1])
AT_CHECK([grep -c -E "(ike|ikev2|esp)=" $ovs_base/right/custom.conf], [0], [6
])

OVS_TRAFFIC_VSWITCHD_STOP()
AT_CLEANUP

AT_SETUP([IPsec -- Libreswan NxN geneve tunnels + reconciliation])
AT_KEYWORDS([ipsec libreswan scale reconciliation])
dnl Note: Geneve test may not work on older kernels due to CVE-2020-25645
Expand Down

0 comments on commit 09d7c5a

Please sign in to comment.