From patchwork Tue Aug 7 16:42:40 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 954579 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="tQE3SByR"; dkim-atps=neutral Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 41lL1b2Td6z9ryt for ; Wed, 8 Aug 2018 02:43:31 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 95E1FD06; Tue, 7 Aug 2018 16:43:24 +0000 (UTC) X-Original-To: ovs-dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id B2BF1D10 for ; Tue, 7 Aug 2018 16:43:22 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pl0-f65.google.com (mail-pl0-f65.google.com [209.85.160.65]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 0D0097C0 for ; Tue, 7 Aug 2018 16:43:20 +0000 (UTC) Received: by mail-pl0-f65.google.com with SMTP id b90-v6so6566079plb.0 for ; Tue, 07 Aug 2018 09:43:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=w85i38FUR87PNS9NHY42bJ3P+2Ipvp2UZPZJRWQDCtI=; b=tQE3SByRT0gzhgajwfiJTdykpjpmWhiOJ57XNU7DCDlLPLGrZvFU5mPWO2SORtLGGl Jc0QOj+DsHolZkEcFWSAbyvzZO3e2Rnsz0b9jjAgT6yPZl9oGMJe7oLBB2Te2Q9sWSco etMXcxCF/pvACwITMWRUtoT3xZOTYAzixaTm00AnHA2dXZ8W/P76Ag7rS2X0Bh/DpKCD KpeDoAAosyp+cBgoDEpng3plcZb80erubnWDt9DqG/3dLPl/7thieigmiAvLDXsRMT/Y edjYxL0MZRmQ1I+SoB6OmjT7n75BIudX8tpjM+/MH+EXbxYNqxmQRy+xgj5Nclwbdtqu qQdQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=w85i38FUR87PNS9NHY42bJ3P+2Ipvp2UZPZJRWQDCtI=; b=XgcBtaPgkUUlECnIZJ1M5zxv+FdtUeNDduSI+U51GZYDVNMFIf3lDjZl4Pvvy5BJiZ 65N4+ukhiz9fiFG9bYkUs37QkwsedsxraS2bXqR+3MjISYpbkZKadXjHdMVRaUCOoIIF nhXIyb/YHPSsh0pSc4xDeSpEIwBmVm/Po3xUk56kBHknnlcVRxSBqb0ahw3vXe1nNdpV 6S539p2gtHVndigDHQjF+lDibB01S6R7qCcx3IFUIU42yonWpEDdb6Yz8RgeEwvFWRuF FPTkY57/6W69YwDOjkuXvsOIL+hc4vwyx+Pq8vpOaCf7HycWpE/kU0ehOTjbEF9hVp3u PLfQ== X-Gm-Message-State: AOUpUlF+2ZBhd6FZV6qg7bxgbW2d9qZWwLkkSluaB8wV3gGFYmPji+VU IZYhejiygoeka9Y/ahDq6m2QtrHl X-Google-Smtp-Source: AAOMgpcp/gJO1v/o3vS7MrkYzqyKvcyriWMxpj0DsaIFttoAKf/cZpEKFAkvGTh9azYfFHl3MUe+4Q== X-Received: by 2002:a17:902:7b87:: with SMTP id w7-v6mr18863849pll.142.1533660200512; Tue, 07 Aug 2018 09:43:20 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id d19-v6sm3489256pgi.50.2018.08.07.09.43.19 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 07 Aug 2018 09:43:20 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Tue, 7 Aug 2018 09:42:40 -0700 Message-Id: <20180807164245.18639-2-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> References: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, FREEMAIL_FROM, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [PATCH v5 1/6] datapath: add transport ports in route lookup for geneve X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org This patch adds transport ports information for route lookup so that IPsec can select geneve tunnel traffic to do encryption. Signed-off-by: Qiuyu Xiao Reviewed-by: Greg Rose Tested-by: Greg Rose --- datapath/linux/compat/geneve.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/datapath/linux/compat/geneve.c b/datapath/linux/compat/geneve.c index 435a23fb7..95a665ddd 100644 --- a/datapath/linux/compat/geneve.c +++ b/datapath/linux/compat/geneve.c @@ -836,7 +836,8 @@ free_dst: static struct rtable *geneve_get_v4_rt(struct sk_buff *skb, struct net_device *dev, struct flowi4 *fl4, - struct ip_tunnel_info *info) + struct ip_tunnel_info *info, + __be16 dport, __be16 sport) { bool use_cache = ip_tunnel_dst_cache_usable(skb, info); struct geneve_dev *geneve = netdev_priv(dev); @@ -850,6 +851,8 @@ static struct rtable *geneve_get_v4_rt(struct sk_buff *skb, memset(fl4, 0, sizeof(*fl4)); fl4->flowi4_mark = skb->mark; fl4->flowi4_proto = IPPROTO_UDP; + fl4->fl4_dport = dport; + fl4->fl4_sport = sport; if (info) { fl4->daddr = info->key.u.ipv4.dst; @@ -895,7 +898,8 @@ static struct rtable *geneve_get_v4_rt(struct sk_buff *skb, static struct dst_entry *geneve_get_v6_dst(struct sk_buff *skb, struct net_device *dev, struct flowi6 *fl6, - struct ip_tunnel_info *info) + struct ip_tunnel_info *info, + __be16 dport, __be16 sport) { bool use_cache = ip_tunnel_dst_cache_usable(skb, info); struct geneve_dev *geneve = netdev_priv(dev); @@ -911,6 +915,8 @@ static struct dst_entry *geneve_get_v6_dst(struct sk_buff *skb, memset(fl6, 0, sizeof(*fl6)); fl6->flowi6_mark = skb->mark; fl6->flowi6_proto = IPPROTO_UDP; + fl6->fl6_dport = dport; + fl6->fl6_sport = sport; if (info) { fl6->daddr = info->key.u.ipv6.dst; @@ -1005,13 +1011,13 @@ static netdev_tx_t geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, goto tx_error; } - rt = geneve_get_v4_rt(skb, dev, &fl4, info); + sport = udp_flow_src_port(geneve->net, skb, 1, USHRT_MAX, true); + rt = geneve_get_v4_rt(skb, dev, &fl4, info, geneve->dst_port, sport); if (IS_ERR(rt)) { err = PTR_ERR(rt); goto tx_error; } - sport = udp_flow_src_port(geneve->net, skb, 1, USHRT_MAX, true); skb_reset_mac_header(skb); iip = ip_hdr(skb); @@ -1097,13 +1103,13 @@ static netdev_tx_t geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, } } - dst = geneve_get_v6_dst(skb, dev, &fl6, info); + sport = udp_flow_src_port(geneve->net, skb, 1, USHRT_MAX, true); + dst = geneve_get_v6_dst(skb, dev, &fl6, info, geneve->dst_port, sport); if (IS_ERR(dst)) { err = PTR_ERR(dst); goto tx_error; } - sport = udp_flow_src_port(geneve->net, skb, 1, USHRT_MAX, true); skb_reset_mac_header(skb); iip = ip_hdr(skb); @@ -1232,13 +1238,17 @@ int ovs_geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) struct geneve_dev *geneve = netdev_priv(dev); struct rtable *rt; struct flowi4 fl4; + __be16 sport; #if IS_ENABLED(CONFIG_IPV6) struct dst_entry *dst; struct flowi6 fl6; #endif + sport = udp_flow_src_port(geneve->net, skb, + 1, USHRT_MAX, true); + if (ip_tunnel_info_af(info) == AF_INET) { - rt = geneve_get_v4_rt(skb, dev, &fl4, info); + rt = geneve_get_v4_rt(skb, dev, &fl4, info, geneve->dst_port, sport); if (IS_ERR(rt)) return PTR_ERR(rt); @@ -1246,7 +1256,7 @@ int ovs_geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) info->key.u.ipv4.src = fl4.saddr; #if IS_ENABLED(CONFIG_IPV6) } else if (ip_tunnel_info_af(info) == AF_INET6) { - dst = geneve_get_v6_dst(skb, dev, &fl6, info); + dst = geneve_get_v6_dst(skb, dev, &fl6, info, geneve->dst_port, sport); if (IS_ERR(dst)) return PTR_ERR(dst); @@ -1257,8 +1267,7 @@ int ovs_geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) return -EINVAL; } - info->key.tp_src = udp_flow_src_port(geneve->net, skb, - 1, USHRT_MAX, true); + info->key.tp_src = sport; info->key.tp_dst = geneve->dst_port; return 0; } From patchwork Tue Aug 7 16:42:41 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 954580 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="YZjiTF1e"; dkim-atps=neutral Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 41lL2747Tcz9s3x for ; Wed, 8 Aug 2018 02:43:59 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 95B2DD12; Tue, 7 Aug 2018 16:43:29 +0000 (UTC) X-Original-To: ovs-dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id AF144CE4 for ; Tue, 7 Aug 2018 16:43:27 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pl0-f47.google.com (mail-pl0-f47.google.com [209.85.160.47]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 346607AA for ; Tue, 7 Aug 2018 16:43:25 +0000 (UTC) Received: by mail-pl0-f47.google.com with SMTP id s17-v6so7346475plp.7 for ; Tue, 07 Aug 2018 09:43:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=eb88L3wwaXsYj62hJLh+hWkjAtqWzDHcavQalmdkrq8=; b=YZjiTF1eD9YKNDU0NOFC12xnoM9UKwRnHW9M+eVkCbW0Vg/qqVlshmhTApALQ1GCFh nqW0kmohcbhAiTldOHY54iNe6TQeXH9zI3LDQiaHuKgLWcpxR3S/WuLX9eN7DBAI3oeZ 9eot7kKI7AMMqzQQbjD5MJFvpIrXN0ZHje9zKhiUmufho7iRW5k9Pf8CocVozHu2ldhT H95rWlRtDZKdWsulsC2W2LRH/KcgWcEYYQkO93x7XNemumMLGXGn1vQj02EahO0hZc0F y+Y0TbRTU0oiYCnFI1z4y6q1zoT0eIiO/tQprGV4KeWiSeje7DdOfQvFjcIrdyxJizV7 drfQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=eb88L3wwaXsYj62hJLh+hWkjAtqWzDHcavQalmdkrq8=; b=Zd+GCTe7NGq5feifz52Gfy/sch9Hx1NpZeE2b0t9DwgJ9jf45J4plcZu/LY8evOOvv dNwuxJMe2kgQ6KBwTak96F9otDDEnrf2YBNWxeUEFPZFYZ9kesSkOWO4IhSljg/3TzXo TJrFvXkIMINE6UEDXkQW4oup32qIZCCgUIfveG/NQ1BnuV8umw1oM+as4n/PkTrgXMxn FjvI8Xv9nbWQLTKMc6MTTAk9H7B6hVsjsMdCiHvLuAoVqJrPrtFqv5a0iVOAVWz6PhdM IpY3jVc/hNX1Hy3fxSVWaO5GGjyfEhWPdWNJYZUCtIuVD2IciZj/m/LtrM1zadv6AomS 4I9w== X-Gm-Message-State: AOUpUlE/V6QUVxGoWLDXDg/Xd3oi+P3un43zS3/RZqtv8h64lI+ysrQD wMxqdsob/TkwlOZ+jd+Rwg66ywAB X-Google-Smtp-Source: AAOMgpfwi4hKWRiMOmiAGjEMxXY+trTnY4OuybleC1nK96aFhni/Ok0E1nE1u76FnrJ+sq1/l/F0LQ== X-Received: by 2002:a17:902:20e3:: with SMTP id v32-v6mr18777478plg.232.1533660203960; Tue, 07 Aug 2018 09:43:23 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id d19-v6sm3489256pgi.50.2018.08.07.09.43.23 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 07 Aug 2018 09:43:23 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Tue, 7 Aug 2018 09:42:41 -0700 Message-Id: <20180807164245.18639-3-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> References: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, FREEMAIL_FROM, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Cc: Ansis Atteka Subject: [ovs-dev] [PATCH v5 2/6] ipsec: reintroduce IPsec support for tunneling X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org This patch reintroduces ovs-monitor-ipsec daemon that was previously removed by commit 2b02d770 ("openvswitch: Allow external IPsec tunnel management.") After this patch, there are no IPsec flavored tunnels anymore. IPsec is enabled by setting up the right values in: 1. OVSDB:Interface:options column; 2. OVSDB:Open_vSwitch:other_config column; 3. OpenFlow pipeline. GRE, VXLAN, GENEVE, and STT IPsec tunnels are supported. LibreSwan and StrongSwan IKE daemons are supported. User can choose pre-shared key, self-signed peer certificate, or CA-signed certificate as authentication method. Signed-off-by: Qiuyu Xiao Signed-off-by: Ansis Atteka Co-authored-by: Ansis Atteka --- Makefile.am | 1 + ipsec/automake.mk | 10 + ipsec/ovs-monitor-ipsec | 1173 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 1184 insertions(+) create mode 100644 ipsec/automake.mk create mode 100755 ipsec/ovs-monitor-ipsec diff --git a/Makefile.am b/Makefile.am index 788972804..aeb2d108f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -481,6 +481,7 @@ include tests/automake.mk include include/automake.mk include third-party/automake.mk include debian/automake.mk +include ipsec/automake.mk include vswitchd/automake.mk include ovsdb/automake.mk include rhel/automake.mk diff --git a/ipsec/automake.mk b/ipsec/automake.mk new file mode 100644 index 000000000..1e530cb42 --- /dev/null +++ b/ipsec/automake.mk @@ -0,0 +1,10 @@ +# 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. + +EXTRA_DIST += \ + ipsec/ovs-monitor-ipsec +FLAKE8_PYFILES += ipsec/ovs-monitor-ipsec diff --git a/ipsec/ovs-monitor-ipsec b/ipsec/ovs-monitor-ipsec new file mode 100755 index 000000000..163b04004 --- /dev/null +++ b/ipsec/ovs-monitor-ipsec @@ -0,0 +1,1173 @@ +#!/usr/bin/env python +# 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 +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" +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} + +""" +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 is destination IPv4 address and 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 is destination IPv4 address and + 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 + +""" + + auth_tmpl = {"psk": Template("""\ + left=$local_ip + right=$remote_ip + authby=psk"""), + "pki_remote": Template("""\ + left=$local_ip + right=$remote_ip + leftid=$local_name + rightid=$remote_name + leftcert=$certificate + rightcert=$remote_cert"""), + "pki_ca": Template("""\ + left=$local_ip + 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 start_ike_daemon(self): + """This function starts 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("Starting StrongSwan") + subprocess.call([self.IPSEC, "start"]) + + def get_active_conns(self): + """This function parses output from 'ipsec status' command. + It returns dictionary where is interface name (as in OVSDB) + and is another dictionary. This another dictionary + uses strongSwan connection name as and more detailed + sample line from the parsed outpus as . """ + + 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+).*", 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(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('%s %s : PSK "%s"\n' % + (tunnel.conf["local_ip"], tunnel.conf["remote_ip"], + tunnel.conf["psk"])) + auth_section = self.auth_tmpl["psk"].substitute(tunnel.conf) + else: + self.secrets_file.write("%s %s : RSA %s\n" % + (tunnel.conf["local_ip"], 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 " 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 " + # command. We use 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) + + auth_tmpl = {"psk": Template("""\ + left=$local_ip + right=$remote_ip + authby=secret"""), + "pki_remote": Template("""\ + left=$local_ip + right=$remote_ip + leftid=@$local_name + rightid=@$remote_name + leftcert="$local_name" + rightcert="$remote_name" + leftrsasigkey=%cert"""), + "pki_ca": Template("""\ + left=$local_ip + right=$remote_ip + leftid=@$local_name + rightid=@$remote_name + leftcert="$local_name" + leftrsasigkey=%cert + rightca=%same""")} + + 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 start_ike_daemon(self): + """This function starts LibreSwan.""" + 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("Starting LibreSwan") + subprocess.call([self.IPSEC, "start"]) + + 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"]: + self._delete_local_certs_and_key(monitor.conf_in_use["pki"]) + + # Load new state + if monitor.conf["pki"]["certificate"]: + self._import_local_certs_and_key(monitor.conf["pki"]) + + monitor.conf_in_use["pki"] = copy.deepcopy(monitor.conf["pki"]) + needs_refresh = True + + # Configure the shunt policy + if monitor.conf["skb_mark"]: + self.conf_file.write(SHUNT_POLICY.format(monitor.conf["skb_mark"])) + + if monitor.conf_in_use["skb_mark"] != monitor.conf["skb_mark"]: + 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('%s %s : PSK "%s"\n' % + (tunnel.conf["local_ip"], 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._import_remote_cert(tunnel.conf["remote_cert"], + tunnel.conf["remote_name"]) + 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"]: + self._delete_remote_cert(tunnel.conf["remote_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 + conn_in = "%s-in-%s" % (name, ver) + conn_out = "%s-out-%s" % (name, ver) + + # 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_in], + 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 + + while True: + proc = subprocess.Popen([self.IPSEC, "auto", "--start", + "--asynchronous", conn_out], + 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 + + # Activate shunt policy if configured + if monitor.conf["skb_mark"]: + subprocess.call([self.IPSEC, "auto", "--start", + "--asynchronous", "prevent_unencrypted_gre"]) + subprocess.call([self.IPSEC, "auto", "--start", + "--asynchronous", "prevent_unencrypted_geneve"]) + subprocess.call([self.IPSEC, "auto", "--start", + "--asynchronous", "prevent_unencrypted_stt"]) + subprocess.call([self.IPSEC, "auto", "--start", + "--asynchronous", "prevent_unencrypted_vxlan"]) + + def get_active_conns(self): + """This function parses output from 'ipsec status' command. + It returns dictionary where is interface name (as in OVSDB) + and is another dictionary. This another dictionary + uses LibreSwan connection name as and more detailed + sample line from the parsed outpus as . """ + + 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+)", conn) + if not m: + continue + + ifname = m.group(1) + if ifname not in conns: + conns[ifname] = {} + (conns[ifname])[conn] = line + + return conns + + def _delete_remote_cert(self, remote_name): + """Delete remote certiticate from the NSS database.""" + try: + proc = subprocess.Popen(['certutil', '-D', '-d', + 'sql:/etc/ipsec.d/', '-n', remote_name], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc.wait() + if proc.returncode: + raise Exception(proc.stderr.read()) + except Exception as e: + vlog.err("Delete remote certificate failed.\n" + str(e)) + + def _import_remote_cert(self, cert, remote_name): + """Import remote certificate to the NSS database.""" + try: + proc = subprocess.Popen(['certutil', '-A', '-a', '-i', cert, + '-d', 'sql:/etc/ipsec.d/', '-n', + remote_name, '-t', 'P,P,P'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc.wait() + if proc.returncode: + raise Exception(proc.stderr.read()) + except Exception as e: + vlog.err("Import remote certificate failed.\n" + str(e)) + + def _delete_local_certs_and_key(self, pki): + """Delete certs and key from the NSS database.""" + name = pki["local_name"] + cacert = pki["ca_cert"] + + 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()) + + if cacert: + proc = subprocess.Popen(['certutil', '-D', '-d', + 'sql:/etc/ipsec.d/', '-n', 'cacert'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc.wait() + if proc.returncode: + raise Exception(proc.stderr.read()) + except Exception as e: + vlog.err("Delete certs and key failed.\n" + str(e)) + + def _import_local_certs_and_key(self, pki): + """Import certs and key to the NSS database.""" + cert = pki["certificate"] + key = pki["private_key"] + name = pki["local_name"] + cacert = pki["ca_cert"] + + try: + # Create p12 file from pem files + proc = subprocess.Popen(['openssl', 'pkcs12', '-export', + '-in', cert, '-inkey', key, '-out', + '/tmp/%s.p12' % name, '-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', '/tmp/%s.p12' % name, + '-d', 'sql:/etc/ipsec.d/', '-W', ''], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc.wait() + if proc.returncode: + raise Exception(proc.stderr.read()) + + if cacert: + proc = subprocess.Popen(['certutil', '-A', '-a', '-i', + cacert, '-d', 'sql:/etc/ipsec.d/', + '-n', 'cacert', '-t', 'CT,,'], + 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)) + subprocess.call(['rm', '-f', '/tmp/%s.p12' % name]) + + +class IPsecTunnel(object): + """This is the base class for IPsec tunnel.""" + + unixctl_config_tmpl = Template("""\ + Tunnel Type: $tunnel_type + Local IP: $local_ip + 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"), + "local_ip": options.get("local_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 not self.conf["local_ip"]: + self.invalid_reason = ": 'local_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 + elif self.conf["remote_name"]: + if not self.conf["certificate"]: + self.invalid_reason = ": must set 'certificate' with PKI" + return False + elif not self.conf["private_key"]: + self.invalid_reason = ": must set 'private_key' with PKI" + return False + if not self.conf["remote_cert"] and not self.conf["ca_cert"]: + self.invalid_reason = ": must set 'remote_cert' or 'ca_cert'" + 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): + 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 + try: + proc = subprocess.Popen([self.IPSEC, "--version"], + stdout=subprocess.PIPE) + line = proc.stdout.readline().strip().split(" ") + + if len(line) >= 2 and line[1] in ["strongSwan", "Libreswan"]: + if line[1] == "strongSwan": + self.ike_helper = StrongSwanHelper(root_prefix) + else: + self.ike_helper = LibreSwanHelper(root_prefix) + else: + raise Exception("IKE daemon is not installed. Please install " + "the supported daemon (StrongSwan or LibreSwan).") + except Exception as e: + vlog.err(str(e)) + sys.exit(1) + + self.ike_helper.start_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""" + # Update PKI certs and key + 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).") + + 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) + + 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) From patchwork Tue Aug 7 16:42:42 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 954581 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="VJWA+qIR"; dkim-atps=neutral Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 41lL360tmTz9ryt for ; Wed, 8 Aug 2018 02:44:50 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 92789D1E; Tue, 7 Aug 2018 16:43:32 +0000 (UTC) X-Original-To: ovs-dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id D0BC2D18 for ; Tue, 7 Aug 2018 16:43:28 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pl0-f52.google.com (mail-pl0-f52.google.com [209.85.160.52]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id E73A9796 for ; Tue, 7 Aug 2018 16:43:26 +0000 (UTC) Received: by mail-pl0-f52.google.com with SMTP id x6-v6so7343066plv.10 for ; Tue, 07 Aug 2018 09:43:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=+h2WrMD4/iayCEclvNznzwWqaimMwgbhWexfi/5GoA4=; b=VJWA+qIR3EbmyIbw5kU/9mjFG6jHOkfM+doa73pURYU3c1buTHOAz7VXlbErx/KRKG /TeydE9fSniNkDnbGHN9YFGjrlhXkKSTYuK/D5Aet8YUVzkvdh+YvIBecIq6c8wFr9MS MCqmNh4BquTemt8scuNuZA5qHeb2KpkR0CPofJOle/VvgsJyJrl24nF0jmylWGeydzDi Rh8t0znsMCBzsFlF/LQ1RYK9B//Erq9r+g+moJ4ZTxw3B2EZq7sIfDpv0yduG+cSx7BY bV2IlbwfmXj8gG6OHzm7qsim6W0DrEtEkYjOkjuExmJEmftHnGEjF/SOIuhjoKjfOt+T 1UfQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=+h2WrMD4/iayCEclvNznzwWqaimMwgbhWexfi/5GoA4=; b=ZDPN/aYqSG1tS4abTAkAr6i/UggFWtK5GrfbYgcCL/j2G7k2KFOSzQx++a+bPwfNdB k55SG/nEovvdP1tJMVO5iE6b19hbV1ZT6JAdsAKQVOH0lzUN7gqUlJWlOXNjaE3V8xAF CZCcoRyCPeg6IJQOw1hX5a56CNVapEoXY3aDvGv97zQDc2V0QprTHwTFgKtloV1X7W5e feCVdvgkJX66Z5Vrd33feh/rlVVEmAh/w82XTgGTAtToH0nWxSD0VizdcVXFOjneGVmf /0l94iUQnCUrkAy5YSgTuHnFHgTb/6uZleXQn5qWev3cq5DKJUr05PUxIQqKfiOYMfoi Hkvw== X-Gm-Message-State: AOUpUlFLhXOU41+99isQyF0lDccVQiOsMfzfcrNsrdTrt7C3NJcdxMeK OJaAJ8oZQ2CMBos1Coqm4tDru36k X-Google-Smtp-Source: AAOMgpcbTrE/6zcpa6P9x/X+nGFp59JIsVOYHLIJ6rM5iFPshlcFmWkT0wf5PpEdx+/S1mJwHbJc5Q== X-Received: by 2002:a17:902:2e83:: with SMTP id r3-v6mr18767623plb.80.1533660206210; Tue, 07 Aug 2018 09:43:26 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id d19-v6sm3489256pgi.50.2018.08.07.09.43.25 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 07 Aug 2018 09:43:25 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Tue, 7 Aug 2018 09:42:42 -0700 Message-Id: <20180807164245.18639-4-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> References: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, FREEMAIL_FROM, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Cc: Ansis Atteka Subject: [ovs-dev] [PATCH v5 3/6] debian and rhel: Create IPsec package. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org Added rules and files to create debian and rpm ovs-ipsec packages. Signed-off-by: Qiuyu Xiao Signed-off-by: Ansis Atteka Co-authored-by: Ansis Atteka --- debian/automake.mk | 3 + debian/control | 21 ++ debian/openvswitch-ipsec.dirs | 1 + debian/openvswitch-ipsec.init | 181 ++++++++++++++++++ debian/openvswitch-ipsec.install | 1 + rhel/automake.mk | 1 + rhel/openvswitch-fedora.spec.in | 19 +- ...b_systemd_system_openvswitch-ipsec.service | 12 ++ utilities/ovs-ctl.in | 18 ++ 9 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 debian/openvswitch-ipsec.dirs create mode 100644 debian/openvswitch-ipsec.init create mode 100644 debian/openvswitch-ipsec.install create mode 100644 rhel/usr_lib_systemd_system_openvswitch-ipsec.service diff --git a/debian/automake.mk b/debian/automake.mk index 4d8e204bb..8a8d43c9f 100644 --- a/debian/automake.mk +++ b/debian/automake.mk @@ -20,6 +20,9 @@ EXTRA_DIST += \ debian/openvswitch-datapath-source.copyright \ debian/openvswitch-datapath-source.dirs \ debian/openvswitch-datapath-source.install \ + debian/openvswitch-ipsec.dirs \ + debian/openvswitch-ipsec.init \ + debian/openvswitch-ipsec.install \ debian/openvswitch-pki.dirs \ debian/openvswitch-pki.postinst \ debian/openvswitch-pki.postrm \ diff --git a/debian/control b/debian/control index 9ae248f27..cde93f20e 100644 --- a/debian/control +++ b/debian/control @@ -322,3 +322,24 @@ Description: Open vSwitch development package 1000V. . This package provides openvswitch headers and libopenvswitch for developers. + +Package: openvswitch-ipsec +Architecture: linux-any +Depends: iproute2, + openvswitch-common (= ${binary:Version}), + openvswitch-switch (= ${binary:Version}), + python, + python-openvswitch (= ${source:Version}), + strongswan, + ${misc:Depends}, + ${shlibs:Depends} +Description: Open vSwitch IPsec tunneling support + Open vSwitch is a production quality, multilayer, software-based, + Ethernet virtual switch. It is designed to enable massive network + automation through programmatic extension, while still supporting + standard management interfaces and protocols (e.g. NetFlow, IPFIX, + sFlow, SPAN, RSPAN, CLI, LACP, 802.1ag). In addition, it is designed + to support distribution across multiple physical servers similar to + VMware's vNetwork distributed vswitch or Cisco's Nexus 1000V. + . + This package provides IPsec tunneling support for OVS tunnels. diff --git a/debian/openvswitch-ipsec.dirs b/debian/openvswitch-ipsec.dirs new file mode 100644 index 000000000..fca44aa7b --- /dev/null +++ b/debian/openvswitch-ipsec.dirs @@ -0,0 +1 @@ +usr/share/openvswitch/scripts \ No newline at end of file diff --git a/debian/openvswitch-ipsec.init b/debian/openvswitch-ipsec.init new file mode 100644 index 000000000..8488beccf --- /dev/null +++ b/debian/openvswitch-ipsec.init @@ -0,0 +1,181 @@ +#!/bin/sh +# +# Copyright (c) 2007, 2009 Javier Fernandez-Sanguino +# +# This is free software; you may redistribute it and/or modify +# it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2, +# or (at your option) any later version. +# +# This is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License with +# the Debian operating system, in /usr/share/common-licenses/GPL; if +# not, write to the Free Software Foundation, Inc., 59 Temple Place, +# Suite 330, Boston, MA 02111-1307 USA +# +### BEGIN INIT INFO +# Provides: openvswitch-ipsec +# Required-Start: $network $local_fs $remote_fs openvswitch-switch +# Required-Stop: $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Open vSwitch GRE-over-IPsec daemon +# Description: The ovs-monitor-ipsec script provides support for +# encrypting GRE tunnels with IPsec. +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +DAEMON=/usr/share/openvswitch/scripts/ovs-monitor-ipsec # Daemon's location +NAME=ovs-monitor-ipsec # Introduce the short server's name here +LOGDIR=/var/log/openvswitch # Log directory to use +DATADIR=/usr/share/openvswitch + +PIDFILE=/var/run/openvswitch/$NAME.pid + +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +DODTIME=10 # Time to wait for the server to die, in seconds + # If this value is set too low you might not + # let some servers to die gracefully and + # 'restart' will not work + +set -e + +running_pid() { +# Check if a given process pid's cmdline matches a given name + pid=$1 + name=$2 + [ -z "$pid" ] && return 1 + [ ! -d /proc/$pid ] && return 1 + cmd=`cat /proc/$pid/cmdline | tr "\000" " "|cut -d " " -f 2` + # Is this the expected server + [ "$cmd" != "$name" ] && return 1 + return 0 +} + +running() { +# Check if the process is running looking at /proc +# (works for all users) + + # No pidfile, probably no daemon present + [ ! -f "$PIDFILE" ] && return 1 + pid=`cat $PIDFILE` + running_pid $pid $DAEMON || return 1 + return 0 +} + +start_server() { + ${DATADIR}/scripts/ovs-ctl start-ovs-ipsec + return 0 +} + +stop_server() { + ${DATADIR}/scripts/ovs-ctl stop-ovs-ipsec + return 0 +} + +force_stop() { +# Force the process to die killing it manually + [ ! -e "$PIDFILE" ] && return + if running ; then + kill -15 $pid + # Is it really dead? + sleep "$DODTIME" + if running ; then + kill -9 $pid + sleep "$DODTIME" + if running ; then + echo "Cannot kill $NAME (pid=$pid)!" + exit 1 + fi + fi + fi + rm -f $PIDFILE +} + + +case "$1" in + start) + log_daemon_msg "Starting $NAME" + # Check if it's running first + if running ; then + log_progress_msg "apparently already running" + log_end_msg 0 + exit 0 + fi + if start_server && running ; then + # It's ok, the server started and is running + log_end_msg 0 + else + # Either we could not start it or it is not running + # after we did + # NOTE: Some servers might die some time after they start, + # this code does not try to detect this and might give + # a false positive (use 'status' for that) + log_end_msg 1 + fi + ;; + stop) + log_daemon_msg "Stopping $NAME" + if running ; then + # Only stop the server if we see it running + stop_server + log_end_msg $? + else + # If it's not running don't do anything + log_progress_msg "apparently not running" + log_end_msg 0 + exit 0 + fi + ;; + force-stop) + # First try to stop gracefully the program + $0 stop + if running; then + # If it's still running try to kill it more forcefully + log_daemon_msg "Stopping (force) $NAME" + force_stop + log_end_msg $? + fi + ;; + restart|force-reload) + log_daemon_msg "Restarting $NAME" + stop_server + # Wait some sensible amount, some server need this + [ -n "$DODTIME" ] && sleep $DODTIME + start_server + running + log_end_msg $? + ;; + status) + log_daemon_msg "Checking status of $NAME" + if running ; then + log_progress_msg "running" + log_end_msg 0 + else + log_progress_msg "apparently not running" + log_end_msg 1 + exit 1 + fi + ;; + # Use this if the daemon cannot reload + reload) + log_warning_msg "Reloading $NAME daemon: not implemented, as the" + log_warning_msg "deamon cannot re-read the config file (use restart)." + ;; + *) + N=/etc/init.d/openvswitch-ipsec + echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" \ + >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/openvswitch-ipsec.install b/debian/openvswitch-ipsec.install new file mode 100644 index 000000000..8fe665cb3 --- /dev/null +++ b/debian/openvswitch-ipsec.install @@ -0,0 +1 @@ +ipsec/ovs-monitor-ipsec usr/share/openvswitch/scripts diff --git a/rhel/automake.mk b/rhel/automake.mk index 7b6c78fd7..bc65d83e5 100644 --- a/rhel/automake.mk +++ b/rhel/automake.mk @@ -35,6 +35,7 @@ EXTRA_DIST += \ rhel/usr_lib_systemd_system_ovn-controller.service \ rhel/usr_lib_systemd_system_ovn-controller-vtep.service \ rhel/usr_lib_systemd_system_ovn-northd.service \ + rhel/usr_lib_systemd_system_openvswitch-ipsec.service \ rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \ rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in index 9f8664e95..ca2b5bc85 100644 --- a/rhel/openvswitch-fedora.spec.in +++ b/rhel/openvswitch-fedora.spec.in @@ -210,6 +210,14 @@ Requires: openvswitch openvswitch-ovn-common %{_py2}-openvswitch %description ovn-docker Docker network plugins for OVN. +%package openvswitch-ipsec +Summary: Open vSwitch IPsec tunneling support +License: ASL 2.0 +Requires: openvswitch %{_py2}-openvswitch libreswan + +%description openvswitch-ipsec +This package provides IPsec tunneling support for OVS tunnels. + %prep %setup -q @@ -261,7 +269,8 @@ install -p -D -m 0644 \ rhel/usr_share_openvswitch_scripts_systemd_sysconfig.template \ $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/openvswitch for service in openvswitch ovsdb-server ovs-vswitchd ovs-delete-transient-ports \ - ovn-controller ovn-controller-vtep ovn-northd; do + ovn-controller ovn-controller-vtep ovn-northd \ + openvswitch-ipsec; do install -p -D -m 0644 \ rhel/usr_lib_systemd_system_${service}.service \ $RPM_BUILD_ROOT%{_unitdir}/${service}.service @@ -319,6 +328,10 @@ install -p -D -m 0755 \ rhel/usr_share_openvswitch_scripts_ovs-systemd-reload \ $RPM_BUILD_ROOT%{_datadir}/openvswitch/scripts/ovs-systemd-reload +install -m 0755 \ + ipsec/ovs-monitor-ipsec \ + $RPM_BUILD_ROOT%{_datadir}/openvswitch/scripts/ovs-monitor-ipsec + # remove unpackaged files rm -f $RPM_BUILD_ROOT%{_bindir}/ovs-parse-backtrace \ $RPM_BUILD_ROOT%{_sbindir}/ovs-vlan-bug-workaround \ @@ -647,6 +660,10 @@ fi %{_mandir}/man8/ovn-controller-vtep.8* %{_unitdir}/ovn-controller-vtep.service +%files openvswitch-ipsec +%{_datadir}/openvswitch/scripts/ovs-monitor-ipsec +%{_unitdir}/openvswitch-ipsec.service + %changelog * Wed Jan 12 2011 Ralf Spenneberg - First build on F14 diff --git a/rhel/usr_lib_systemd_system_openvswitch-ipsec.service b/rhel/usr_lib_systemd_system_openvswitch-ipsec.service new file mode 100644 index 000000000..813844e51 --- /dev/null +++ b/rhel/usr_lib_systemd_system_openvswitch-ipsec.service @@ -0,0 +1,12 @@ +[Unit] +Description=OVS IPsec daemon +Requires=openvswitch.service +After=openvswitch.service + +[Service] +Type=forking +ExecStart=/usr/share/openvswitch/scripts/ovs-ctl start-ovs-ipsec +ExecStop=/usr/share/openvswitch/scripts/ovs-ctl stop-ovs-ipsec + +[Install] +WantedBy=multi-user.target diff --git a/utilities/ovs-ctl.in b/utilities/ovs-ctl.in index 43c8f32b7..d9b6ed943 100755 --- a/utilities/ovs-ctl.in +++ b/utilities/ovs-ctl.in @@ -222,6 +222,13 @@ start_forwarding () { return 0 } +start_ovs_ipsec () { + ${datadir}/scripts/ovs-monitor-ipsec \ + --pidfile=${rundir}/ovs-monitor-ipsec.pid \ + --log-file --detach --monitor unix:${rundir}/db.sock + return 0 +} + ## ---- ## ## stop ## ## ---- ## @@ -238,6 +245,11 @@ stop_forwarding () { fi } +stop_ovs_ipsec () { + ${bindir}/ovs-appctl -t ovs-monitor-ipsec exit + return 0 +} + ## --------------- ## ## enable-protocol ## ## --------------- ## @@ -522,6 +534,12 @@ case $command in delete-transient-ports) del_transient_ports ;; + start-ovs-ipsec) + start_ovs_ipsec + ;; + stop-ovs-ipsec) + stop_ovs_ipsec + ;; help) usage ;; From patchwork Tue Aug 7 16:42:43 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 954582 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="S3kEkgSr"; dkim-atps=neutral Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 41lL3f5hH2z9ryt for ; Wed, 8 Aug 2018 02:45:18 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 7AC4DD15; Tue, 7 Aug 2018 16:43:34 +0000 (UTC) X-Original-To: ovs-dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id B7F10D1F for ; Tue, 7 Aug 2018 16:43:32 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pl0-f66.google.com (mail-pl0-f66.google.com [209.85.160.66]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 92307796 for ; Tue, 7 Aug 2018 16:43:29 +0000 (UTC) Received: by mail-pl0-f66.google.com with SMTP id b90-v6so6566246plb.0 for ; Tue, 07 Aug 2018 09:43:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=dkUvvXDqFhmczIf8cX/ZzSjN5VXYSnm3nAPX64nimmk=; b=S3kEkgSria4/n32jb8vXAPGttgGwSYd0zuBMhFBJrsduOi6tZtW4YzH9VJLXtSMsAB oKtk1d/C4chPgLEk9LNTxZaTjINfd+2TUEJvPOi/QJvNFgIUHKXGXvpAsUEdy4l1iOTa E6gFGtRvS5I7Y4rPV7W4WZwC06N6HE64TWrAS0Lk7CRhKXY4eD2/46f8Imx1rnsuTLxe rqE5G6VTAIDX5qo4We05z5PIt1yQOLfl1wOutsr/gOHpT5Hm9EijhNTd3eKkghhL12AX yFvGj8SN0XGhPFlbJJ7TuFUqVRDZLdiTCdtvVAt7EO/z+Uw4KSsP2PWj3f1D3XZMwYwc a54Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=dkUvvXDqFhmczIf8cX/ZzSjN5VXYSnm3nAPX64nimmk=; b=eHq6DU4kHsBSzuv9BJjmxmpeeg77zKz1qbtXzNWoUBnMA+iyUw239578e5zSlSq6xY AlMcwuWpcS1Mt3IWuUyq71vmYVG1GYXgX0l/DH4ohHPUJKwJsSBCDNoC5IflUroDKSfR 3qN/rBR5+rEFzSmk2a87oLpZsIaV1XX1qJWezmjItDbkhE07QhG8H4GsjV60DEnBOtcf U+5iFwBXB046YHesPd1+4g7emmHZwbdxBQbYmumb5CMl7FGYTv+HZs+RHXnWzIgo+sPO T51wIM8Kz0qUQKWiZNEFVNqUzbOwgjVAi5sfNBExtco/vpFmyivXs7Jt4MsN+QHHjYSn dXwQ== X-Gm-Message-State: AOUpUlHIZ6TtjaMbm98++551ovNXSgwpDBoqNyldf8xbduQlglDpa+i4 Tsbe7YLoWMSYce/6VbVwSwjMfGOl X-Google-Smtp-Source: AAOMgpewgbLmTuzg2T5mxlNY7Vyo0bKgLgiaadbQZxH7NKWjM09XsPtJe6bGmul+Gyvb2RUUDPTgtQ== X-Received: by 2002:a17:902:b902:: with SMTP id bf2-v6mr18329678plb.160.1533660208574; Tue, 07 Aug 2018 09:43:28 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id d19-v6sm3489256pgi.50.2018.08.07.09.43.27 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 07 Aug 2018 09:43:28 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Tue, 7 Aug 2018 09:42:43 -0700 Message-Id: <20180807164245.18639-5-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> References: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, FREEMAIL_FROM, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Cc: Ansis Atteka Subject: [ovs-dev] [PATCH v5 4/6] Documentation: IPsec tunnel tutorial and documentation. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org tutorials/index.rst gives a step-by-setp guide to set up OVS IPsec tunnel. tutorials/ipsec.rst gives detailed explanation on the IPsec tunnel configuration methods and forwarding modes. Signed-off-by: Qiuyu Xiao Signed-off-by: Ansis Atteka Co-authored-by: Ansis Atteka --- Documentation/automake.mk | 2 + Documentation/howto/index.rst | 1 + Documentation/howto/ipsec.rst | 204 +++++++++++++++++ Documentation/index.rst | 3 +- Documentation/tutorials/index.rst | 1 + Documentation/tutorials/ipsec.rst | 353 ++++++++++++++++++++++++++++++ vswitchd/vswitch.xml | 157 ++++++++++++- 7 files changed, 711 insertions(+), 10 deletions(-) create mode 100644 Documentation/howto/ipsec.rst create mode 100644 Documentation/tutorials/ipsec.rst diff --git a/Documentation/automake.mk b/Documentation/automake.mk index 244479490..5401b9bad 100644 --- a/Documentation/automake.mk +++ b/Documentation/automake.mk @@ -28,6 +28,7 @@ DOC_SOURCE = \ Documentation/tutorials/ovn-openstack.rst \ Documentation/tutorials/ovn-sandbox.rst \ Documentation/tutorials/ovs-conntrack.rst \ + Documentation/tutorials/ipsec.rst \ Documentation/topics/index.rst \ Documentation/topics/bonding.rst \ Documentation/topics/idl-compound-indexes.rst \ @@ -60,6 +61,7 @@ DOC_SOURCE = \ Documentation/howto/docker.rst \ Documentation/howto/dpdk.rst \ Documentation/howto/firewalld.rst \ + Documentation/howto/ipsec.rst \ Documentation/howto/kvm.rst \ Documentation/howto/libvirt.rst \ Documentation/howto/selinux.rst \ diff --git a/Documentation/howto/index.rst b/Documentation/howto/index.rst index 201d6936b..9a3487be3 100644 --- a/Documentation/howto/index.rst +++ b/Documentation/howto/index.rst @@ -37,6 +37,7 @@ OVS :maxdepth: 2 kvm + ipsec selinux libvirt ssl diff --git a/Documentation/howto/ipsec.rst b/Documentation/howto/ipsec.rst new file mode 100644 index 000000000..d3fb190be --- /dev/null +++ b/Documentation/howto/ipsec.rst @@ -0,0 +1,204 @@ +.. + 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. + + Convention for heading levels in Open vSwitch documentation: + + ======= Heading 0 (reserved for the title in a document) + ------- Heading 1 + ~~~~~~~ Heading 2 + +++++++ Heading 3 + ''''''' Heading 4 + + Avoid deeper levels because they do not render well. + +======================================= +Encrypt Open vSwitch Tunnels with IPsec +======================================= + +This document gives detailed description on the OVS IPsec tunnel and its +configuration modes. If you want to follow a step-by-step guide to run and +test IPsec tunnel, please refer to :doc:`/tutorials/ipsec`. + +Overview +-------- + +Why do encryption? +~~~~~~~~~~~~~~~~~~ + +OVS tunnel packets are transported from one machine to another. Along the path, +the packets are processed by physical routers and physical switches. There are +risks that these physical devices might read or write the contents of the +tunnel packets. IPsec encrypts IP payload and prevents the malicious party +sniffing or manipulating the tunnel traffic. + +OVS IPsec +~~~~~~~~~ + +OVS IPsec aims to provide a simple interface for user to add encryption on OVS +tunnels. It supports GRE, GENEVE, VXLAN, and STT tunnel. The IPsec +configuration is done by setting options of the tunnel interface and +other_config of Open_vSwitch. You can choose different authentication methods +and plaintext tunnel policies based on your requirements. + +OVS does not currently provide any support for IPsec encryption for traffic not +encapsulated in a tunnel. + +Configuration +------------- + +Authentication Methods +~~~~~~~~~~~~~~~~~~~~~~ + +Hosts of the IPsec tunnel need to authenticate each other to build a secure +channel. There are three authentication methods: + +1) You can use a pre-shared key (PSK) to do authentication. In both hosts, set + the same PSK value. This PSK is like your password. You should never reveal + it to untrusted parties. This method is easier to use but less secure than + the certificate-based methods:: + + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:psk=swordfish + + .. note:: + + The ``local_ip`` field is required for the IPsec tunnel. + +2) You can use a self-signed certificate to do authentication. In each host, + generate a certificate and the paired private key. Copy the certificate of + the remote host to the local host and configure the OVS as following:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/path/to/local_cert.pem \ + other_config:private_key=/path/to/priv_key.pem + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:remote_cert=/path/to/remote_cert.pem + + `local_cert.pem` is the certificate of the local host. `priv_key.pem` + is the private key of the local host. `priv_key.pem` needs to be stored in + a secure location. `remote_cert.pem` is the certificate of the remote host. + + .. note:: + + OVS IPsec requires x.509 version 3 certificate with the subjectAltName + DNS field setting the same string as the common name (CN) field. You can + follow the tutorial in :doc:`/tutorials/ipsec` and use ovs-pki(8) to + generate compatible certificate and key. + + (Before OVS version 2.10.90, ovs-pki(8) did not generate x.509 v3 + certificates, so if your existing PKI was generated by an older version, + it is not suitable for this purpose.) + +3) You can also use CA-signed certificate to do authentication. First, you need + to create a CA certificate and sign each host certificate with the CA key + (please see :doc:`/tutorials/ipsec`). Copy the CA certificate to each + host and configure the OVS as following:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/path/to/local_cert.pem \ + other_config:private_key=/path/to/priv_key.pem \ + other_config:ca_cert=/path/to/ca_cert.pem + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:remote_name=remote_cn + + `ca_cert.pem` is the CA certificate. You need to set `remote_cn` as the + common name (CN) of the remote host's certificate so that only the + certificate with the expected CN can be trusted in this connection. It is + preferable to use this method than 2) if there are many remote hosts since + you don't have to copy every remote certificate to the local host. + + .. note:: + + When using certificate-based authentication, you should not set psk in + the interface options. When using psk-based authentication, you should + not set certificate, private_key, ca_cert, remote_cert, and remote_name. + +Plaintext Policies +~~~~~~~~~~~~~~~~~~ + +When an IPsec tunnel is configured in this database, multiple independent +components take responsibility for implementing it. ``ovs-vswitchd`` and its +datapath handle packet forwarding to the tunnel and a separate daemon pushes +the tunnel's IPsec policy configuration to the kernel or other entity that +implements it. There is a race: if the former configuration completes before +the latter, then packets sent by the local host over the tunnel can be +transmitted in plaintext. Using this setting, OVS users can avoid this +undesirable situation. + +1) The default setting allows unencrypted packets to be sent before IPsec + completes negotiation:: + + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:psk=swordfish + + This setting should be used only and only if tunnel configuration is static + and/or if there is firewall that can drop the plain packets that + occasionally leak the tunnel unencrypted on OVSDB (re)configuration events. + +2) Setiing ipsec_skb_mark drops unencrypted packets by using skb_mark of + tunnel packets:: + + $ ovs-vsctl set Open_vSwitch . other_config:ipsec_skb_mark=0/1 + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:psk=swordfish + + OVS IPsec drops unencrypted packets which carry the same skb_mark as + `ipsec_skb_mark`. By setting the ipsec_skb_mark as 0/1, OVS IPsec prevents + all unencrypted tunnel packets leaving the host since the default skb_mark + value for tunnel packets are 0. This affects all OVS tunnels including those + without IPsec being set up. You can install OpenFlow rules to whitelist + those non-IPsec tunnels by setting the skb_mark of the tunnel traffic as + non-zero value. + +3) Setting `ipsec_skb_mark` as 1/1 only drops tunnel packets with skb_mark + value being 1:: + + $ ovs-vsctl set Open_vSwitch . other_config:ipsec_skb_mark=1/1 + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:psk=swordfish + + Opposite to 2), this setting passes through unencrypted tunnel packets by + default. To drop unencrypted IPsec tunnel traffic, you need to explicitly + set skb_mark to a non-zero value for those tunnel traffic by installing + OpenFlow rules. + +Bug Reporting +------------- + +If you think you may have found a bug with security implications, like + +1) IPsec protected tunnel accepted packets that came unencrypted; OR +2) IPsec protected tunnel allowed packets to leave unencrypted + +then please report such bugs according to :doc:`/internals/security`. + +If the bug does not have security implications, then report it according to +instructions in :doc:`/internals/bugs`. diff --git a/Documentation/index.rst b/Documentation/index.rst index 06602f728..bab5ba1f1 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -65,7 +65,8 @@ vSwitch? Start here. :doc:`tutorials/ovs-advanced` | :doc:`tutorials/ovn-sandbox` | :doc:`tutorials/ovn-openstack` | - :doc:`tutorials/ovs-conntrack` + :doc:`tutorials/ovs-conntrack` | + :doc:`tutorials/ipsec` Deeper Dive ----------- diff --git a/Documentation/tutorials/index.rst b/Documentation/tutorials/index.rst index ab90b7c84..b481090a0 100644 --- a/Documentation/tutorials/index.rst +++ b/Documentation/tutorials/index.rst @@ -40,6 +40,7 @@ vSwitch. :maxdepth: 2 faucet + ipsec ovs-advanced ovn-sandbox ovn-openstack diff --git a/Documentation/tutorials/ipsec.rst b/Documentation/tutorials/ipsec.rst new file mode 100644 index 000000000..492ed223c --- /dev/null +++ b/Documentation/tutorials/ipsec.rst @@ -0,0 +1,353 @@ +.. + 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. + + Convention for heading levels in Open vSwitch documentation: + + ======= Heading 0 (reserved for the title in a document) + ------- Heading 1 + ~~~~~~~ Heading 2 + +++++++ Heading 3 + ''''''' Heading 4 + + Avoid deeper levels because they do not render well. + +================== +OVS IPsec Tutorial +================== + +This document provides a step-by-step guide for running IPsec tunnel in Open +vSwitch. A more detailed description on OVS IPsec tunnel and its +configuration modes can be found in :doc:`/howto/ipsec`. + +Requirements +------------ + +OVS IPsec tunnel requires Linux kernel (>= v3.10.0) and OVS out-of-tree kernel +module. The compatible IKE daemons are LibreSwan (>= v3.23) and StrongSwan +(>= v5.3.5). + +.. _install-ovs-ipsec: + +Installing OVS and IPsec Packages +--------------------------------- + +OVS IPsec has .deb and .rpm packages. You should use the right package +based on your Linux distribution. This tutorial uses Ubuntu 16.04 and Fedora 27 +as examples. + +Ubuntu +~~~~~~ + +1. Follow :doc:`/intro/install/debian` to build debian packages. + + .. note:: + + If you have already installed OVS, then you only need to install + openvswitch-pki_*.deb and openvswitch-ipsec_*.deb in the following step. + If your kernel version is below v4.13.0, update your kernel to v4.13.0 or + above. + +2. Install the related packages:: + + $ apt-get install dkms strongswan + $ dpkg -i libopenvswitch_*.deb openvswitch-common_*.deb \ + openvswitch-switch_*.deb openvswitch-datapath-dkms_*.deb \ + python-openvswitch_*.deb openvswitch-pki_*.deb \ + openvswitch-ipsec_*.deb + + If the installation is successful, you should be able to see the + ovs-monitor-ipsec daemon is running in your system. + +Fedora +~~~~~~ + +1. Follow :doc:`/intro/install/fedora` to build RPM packages. + +2. Install the related packages:: + + $ dnf install python2-openvswitch libreswan \ + "kernel-devel-uname-r == $(uname -r)" + $ rpm -i openvswitch-*.rpm openvswitch-kmod-*.rpm \ + openvswitch-openvswitch-ipsec-*.rpm + +3. Install firewall rules to allow ESP traffic:: + + $ iptables -A IN_FedoraServer_allow -p esp -j ACCEPT + +4. Run the openvswitch-ipsec service:: + + $ systemctl start openvswitch-ipsec.service + + .. note:: + + The SELinux policies might prevent openvswitch-ipsec.service to access + certain resources. You can configure SELinux to remove such restrictions. + +Configuring IPsec tunnel +------------------------ + +Suppose you want to build IPsec tunnel between two hosts. Assume `host_1`'s +external IP is 1.1.1.1, and `host_2`'s external IP is 2.2.2.2. Make sure +`host_1` and `host_2` can ping each other via these external IPs. + +0. Set up some variables to make life easier. On both hosts, set ``ip_1`` and + ``ip_2`` variables, e.g.:: + + $ ip_1=1.1.1.1 + $ ip_2=2.2.2.2 + +1. Set up OVS bridges in both hosts. + + In `host_1`:: + + $ ovs-vsctl add-br br-ipsec + $ ip addr add 192.0.0.1/24 dev br-ipsec + $ ip link set br-ipsec up + + In `host_2`:: + + $ ovs-vsctl add-br br-ipsec + $ ip addr add 192.0.0.2/24 dev br-ipsec + $ ip link set br-ipsec up + +2. Set up IPsec tunnel. + + There are three authentication methods. You can choose one to set up your + IPsec tunnel. + + a) Using pre-shared key: + + In `host_1`:: + + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=$ip_1 \ + options:remote_ip=$ip_2 \ + options:psk=swordfish + + In `host_2`:: + + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=$ip_2 \ + options:remote_ip=$ip_1 \ + options:psk=swordfish + + .. note:: + + Pre-shared key (PSK) based authentication is easy to set up but less + secure compared with other authentication methods. You should use it + cautiously in production systems. + + b) Using self-signed certificate: + + Generate self-signed certificate in both `host_1` and `host_2`. Then copy + the certificate of `host_1` to `host_2` and the certificate of `host_2` + to `host_1`. + + In `host_1`:: + + $ ovs-pki req -u host_1 + $ ovs-pki self-sign host_1 + $ scp host_1-cert.pem $ip_2:/etc/keys/host_1-cert.pem + + In `host_2`:: + + $ ovs-pki req -u host_2 + $ ovs-pki self-sign host_2 + $ scp host_2-cert.pem $ip_1:/etc/keys/host_2-cert.pem + + .. note:: + + If you use StrongSwan as IKE daemon, please move the host certificates + to /etc/ipsec.d/certs/ and private key to /etc/ipsec.d/private/ so that + StrongSwan has permission to access those files. + + Configure IPsec tunnel to use self-signed certificates. + + In `host_1`:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/etc/keys/host_1-cert.pem \ + other_config:private_key=/etc/keys/host_1-privkey.pem + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=$ip_1 \ + options:remote_ip=$ip_2 \ + options:remote_cert=/etc/keys/host_2-cert.pem + + In `host_2`:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/etc/keys/host_2-cert.pem \ + other_config:private_key=/etc/keys/host_2-privkey.pem + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=$ip_2 \ + options:remote_ip=$ip_1 \ + options:remote_cert=/etc/keys/host_1-cert.pem + + .. note:: + + The confidentiality of the private key is very critical. Don't copy it + to places where it might be compromised. (The certificate need not be + kept confidential.) + + c) Using CA-signed certificate: + + First you need to establish a public key infrastructure (PKI). Suppose + you choose `host_1` to host PKI. + + In `host_1`:: + + $ ovs-pki init + + Generate certificate requests and copy the certificate request of + `host_2` to `host_1`. + + In `host_1`:: + + $ ovs-pki req -u host_1 + + In `host_2`:: + + $ ovs-pki req -u host_2 + $ scp host_2-req.pem $ip_1:/etc/keys/host_2-req.pem + + Sign the certificate requests with the CA key. Copy `host_2`'s signed + certificate and the CA certificate to `host_2`. + + In `host_1`:: + + $ ovs-pki sign host_1 switch + $ ovs-pki sign host_2 switch + $ scp host_2-cert.pem $ip_2:/etc/keys/host_2-cert.pem + $ scp /var/lib/openvswitch/pki/switchca/cacert.pem \ + $ip_2:/etc/keys/cacert.pem + + .. note:: + + If you use StrongSwan as IKE daemon, please move the host certificates + to /etc/ipsec.d/certs/, CA certificate to /etc/ipsec.d/cacerts/, and + private key to /etc/ipsec.d/private/ so that StrongSwan has permission + to access those files. + + Configure IPsec tunnel to use CA-signed certificate. + + In `host_1`:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/etc/keys/host_1-cert.pem \ + other_config:private_key=/etc/keys/host_1-privkey.pem \ + other_config:ca_cert=/etc/keys/cacert.pem + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=$ip_1 \ + options:remote_ip=$ip_2 \ + options:remote_name=host_2 + + In `host_2`:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/etc/keys/host_2-cert.pem \ + other_config:private_key=/etc/keys/host_2-privkey.pem \ + other_config:ca_cert=/etc/keys/cacert.pem + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=$ip_2 \ + options:remote_ip=$ip_1 \ + options:remote_name=host_1 + + .. note:: + + remote_name is the common name (CN) of the signed-certificate. It must + match the name given as the argument to the ``ovs-pki sign command``. + It ensures that only certificate with the expected CN can be + authenticated; otherwise, any certificate signed by the CA would be + accepted. + +3. Test IPsec tunnel. + + Now you should have an IPsec GRE tunnel running between two hosts. To verify + it, in `host_1`:: + + $ ping 192.0.0.2 & + $ tcpdump -ni any net $ip_2 + + You should be able to see that ESP packets are being sent from `host_1` to + `host_2`. + +Troubleshooting +--------------- + +The ``ovs-monitor-ipsec`` daemon manages and monitors the IPsec tunnel state. +Use the following ``ovs-appctl`` command to view ``ovs-monitor-ipsec`` internal +representation of tunnel configuration:: + + $ ovs-appctl -t ovs-monitor-ipsec tunnels/show + +If there is misconfiguration, then ``ovs-appctl`` should indicate why. +For example:: + + Interface name: gre0 v5 (CONFIGURED) <--- Should be set to CONFIGURED. + Otherwise, error message will + be provided + Tunnel Type: gre + Local IP: 1.1.1.1 + Remote IP: 2.2.2.2 + SKB mark: None + Local cert: None + Local name: None + Local key: None + Remote cert: None + Remote name: None + CA cert: None + PSK: swordfish + Ofport: 1 <--- Whether ovs-vswitchd has assigned Ofport + number to this Tunnel Port + CFM state: Up <--- Whether CFM declared this tunnel healthy + Kernel policies installed: + ... <--- IPsec policies for this OVS tunnel in + Linux Kernel installed by strongSwan + Kernel security associations installed: + ... <--- IPsec security associations for this OVS + tunnel in Linux Kernel installed by + strongswan + IPsec connections that are active: + ... <--- IPsec "connections" for this OVS + tunnel + +If you don't see any active connections, try to run the following command to +refresh the ``ovs-monitor-ipsec`` daemon:: + + $ ovs-appctl -t ovs-monitor-ipsec refresh + +You can also check the logs of the ``ovs-monitor-ipsec`` daemon and the IKE +daemon to locate issues. ``ovs-monitor-ipsec`` outputs log messages to +/var/log/openvswitch/ovs-monitor-ipsec.log. + +Bug Reporting +------------- + +If you think you may have found a bug with security implications, like + +1. IPsec protected tunnel accepted packets that came unencrypted; OR +2. IPsec protected tunnel allowed packets to leave unencrypted; + +Then report such bugs according to :doc:`/internals/security`. + +If bug does not have security implications, then report it according to +instructions in :doc:`/internals/bugs`. + +If you have suggestions to improve this tutorial, please send a email to +ovs-discuss@openvswitch.org. diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml index 634294944..e9a8d20fe 100644 --- a/vswitchd/vswitch.xml +++ b/vswitchd/vswitch.xml @@ -503,10 +503,8 @@ OpenFlow specification mandates the timeout to be at least one second. The default is 10 seconds.

- - + - Sequence number for client to increment. When a client modifies @@ -770,6 +768,118 @@ + +

+ These settings control the global configuration of IPsec tunnels. The + options column of the Interface table + configures IPsec for individual tunnels. +

+

+ OVS IPsec supports the following three forms of authentication. + Currently, all IPsec tunnels must use the same form: +

+
    +
  1. + Pre-shared keys: Omit the global settings. On each tunnel, set . +
  2. +
  3. + Self-signed certificates: Set the private_key and + certificate global settings. On each tunnel, set . The remote certificate can be + self-signed. +
  4. +
  5. + CA-signed certificates: Set all of the global settings. On each + tunnel, set to the common + name (CN) of the remote certificate. The remote certificate must be + signed by the CA. +
  6. +
+ +

+ Name of a PEM file containing the private key used as the switch's + identity for IPsec tunnels. +

+
+ +

+ Name of a PEM file containing a certificate that certifies the + switch's private key, and identifies a trustworthy switch for IPsec + tunnels. The certificate must be x.509 version 3 and with the + string in common name (CN) also set in the subject alternative name + (SAN). +

+
+ +

+ Name of a PEM file containing the CA certificate used to verify + that a remote switch of the IPsec tunnel is trustworthy. +

+
+ + +

+ When an IPsec tunnel is configured in this database, multiple + independent components take responsibility for implementing it. + ovs-vswitchd and its datapath handle packet forwarding + to the tunnel and a separate daemon pushes the tunnel's IPsec policy + configuration to the kernel or other entity that implements it. + There is a race: if the former configuration completes before the + latter, then packets sent by the local host over the tunnel can be + transmitted in plaintext. Using this setting, OVS users can avoid + this undesirable situation. +

+ +

+ This setting takes the form + value/mask. If it is specified, + then the skb_mark field in every outgoing tunneled + packet sent in plaintext is compared against it and, if it matches, + the packet is dropped. This is a global setting that is applied to + every tunneled packet, regardless of whether IPsec encryption is + enabled for the tunnel, the type of tunnel, or whether OVS is + involved. +

+ +

+ Example policies: +

+ +
+
1/1
+
+ Drop all unencrypted tunneled packets in which the + least-significant bit of skb_mark is 1. This would + be a useful policy given an OpenFlow flow table that sets + skb_mark to 1 for traffic that should be encrypted. + The default skb_mark is 0, so this would not affect + other traffic. +
+ +
0/1
+
+ Drop all unencrypted tunneled packets in which the + least-significant bit of skb_mark is 0. This would + be a useful policy if no unencrypted tunneled traffic should exit + the system without being specially whitelisted by setting + skb_mark to 1. +
+ +
(empty)
+
+ If this setting is empty or unset, then all unencrypted tunneled + packets are transmitted in the usual way. +
+
+
+
+
+ The overall purpose of these columns is described under Common Columns at the beginning of this document. @@ -1409,7 +1519,7 @@ - +

A port within a .

Most commonly, a port has exactly one ``interface,'' pointed to by its @@ -2398,9 +2508,9 @@

- Optional. The tunnel destination IP that received packets must - match. Default is to match all addresses. If specified, may be one - of: + Normally optional; required for IPsec tunnels. The tunnel + destination IP that received packets must match. Default is to match + all addresses. If specified, may be one of:

    @@ -2655,7 +2765,7 @@

    - Optional. Compute encapsulation header (either GRE or UDP) + Optional. Compute encapsulation header (either GRE or UDP) checksums on outgoing packets. Default is disabled, set to true to enable. Checksums present on incoming packets will be validated regardless of this setting. @@ -2673,8 +2783,37 @@ - + +

    + Setting any of these options enables IPsec support for a given + tunnel. gre, geneve, vxlan, + and stt interfaces support these options. See the + IPsec section in the table + for a description of each mode. +

    + +

    + In PSK mode only, the preshared secret to negotiate tunnel. This + value must match on both tunnel ends. +

    +
    + +

    + In self-signed certificate mode only, name of a PEM file + containing a certificate of the remote switch. The certificate + must be x.509 version 3 and with the string in common name (CN) + also set in the subject alternative name (SAN). +

    +
    + +

    + In CA-signed certificate mode only, common name (CN) of the remote + certificate. +

    +
    + +

    Only erspan interfaces support these options. From patchwork Tue Aug 7 16:42:44 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 954583 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="d9tx/sNv"; dkim-atps=neutral Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 41lL4N4FFtz9ryt for ; Wed, 8 Aug 2018 02:45:56 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 9CB1BD32; Tue, 7 Aug 2018 16:43:36 +0000 (UTC) X-Original-To: ovs-dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id 05D92D25 for ; Tue, 7 Aug 2018 16:43:35 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pg1-f195.google.com (mail-pg1-f195.google.com [209.85.215.195]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 2A40F799 for ; Tue, 7 Aug 2018 16:43:34 +0000 (UTC) Received: by mail-pg1-f195.google.com with SMTP id x5-v6so8105246pgp.7 for ; Tue, 07 Aug 2018 09:43:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=ILUXdX/zC7qGtY8uZvX3bO3TiSShKn9T0oQu413nxt0=; b=d9tx/sNvKIJ8g2hb6GsMm6jhfXfjrVkSyHUWY2ze5Qt1kMGsmO83/10XeBVi1f6gxJ atMkklnZrCKMM2SI1fmx2HZROJGAgosj7B+TQmhs7KjgZpdXXra1jtNOfdRBSdz2sn72 S/UdLj24D9Dkj/3E+ToiEAoKgwzcvs+lp71l6aup0Fn1urceJ4aPlNS9eCpncvrESw1A f+4fd6ta22zW4CwazXN89DOOBDCNtQG+a8GqkrKu0OO35UOMb59lcIP6nqGd2lJTD52k GexVY8IrM5MTFwZ9bZ/KsKaGXjbaF1Uw945Hpb7muD95LtR6YCCm+n6gosI9BvF4IWU7 gNlg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=ILUXdX/zC7qGtY8uZvX3bO3TiSShKn9T0oQu413nxt0=; b=IaeC2EDqTrL/YiRKAvSstba8AHDOj209q3EhVzW/CiJmoZ63qRPsQjUV7UsWPMCUyN oQ2qr84XHFlbSr7c1T8FIp9sRwEXaTerKI13ZRo8dZdNbJiI4tquzTMMshclodd0J6aD ZZy1XD5GOBHFikIdMCNn5rNs+075lqgtyRXl8jeI0uPQZKz7RAjYajCy5DpuPUbhiuzX wYqEGQuCgmNK789t5DxuXyg4ME3PoV6jCm8CCfJoKJYox0kgB4274i2ee4CehDKz0G/j G5U6c/kZMh2VLTwy8SpDHT6gTEari42vY2Oxtlhr4S61k7VFGvelwVDlkIn07YCg4l+z 2zKQ== X-Gm-Message-State: AOUpUlGJbQpx7l//jkhYhfK4bTHWCcHQjhpoPmsYTL3j0HeXHkC/Q7Jk zVhMV3PLhUFQfqdAwxvdfJH20NdB X-Google-Smtp-Source: AAOMgpfN2oVkaXI9QRM84kOcIYoXp38u/77SN2FWgSRfGpMGzY6q4eJ46mIV1BJwzR90i9cVDztOPQ== X-Received: by 2002:a62:7086:: with SMTP id l128-v6mr8196753pfc.144.1533660213468; Tue, 07 Aug 2018 09:43:33 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id d19-v6sm3489256pgi.50.2018.08.07.09.43.32 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 07 Aug 2018 09:43:33 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Tue, 7 Aug 2018 09:42:44 -0700 Message-Id: <20180807164245.18639-6-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> References: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, FREEMAIL_FROM, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [PATCH v5 5/6] OVN: native support for tunnel encryption X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org This patch adds IPsec support for OVN tunnel. Basically, OVN offers a binary option to its user for encryption configuration. If the IPsec option is turned on, all tunnels will be encrypted. Otherwise, no tunnel will be encrypted. The changes are summarized as below: 1) Added a ipsec column on the NB_Global table and SB_Global table. The value of ipsec column is propagated by ovn-northd from NB_Global to SB_Global. 2) ovn-controller monitors the ipsec column in SB_Global. If the ipsec value is true, ovn-controller sets options of the tunnel interface by specifying "options:remote_name=". If the ipsec value is false, ovn-controller removes these options. 3) ovs-monitor-ipsec daemon (https://mail.openvswitch.org/pipermail/ovs-dev/2018-June/348701.html) monitors the tunnel interface options and configures IKE daemon accordingly for IPsec encryption. Signed-off-by: Qiuyu Xiao --- ovn/controller/encaps.c | 31 ++++++++++++++++++++++---- ovn/controller/encaps.h | 7 +++++- ovn/controller/ovn-controller.c | 4 +++- ovn/northd/ovn-northd.c | 8 +++++-- ovn/ovn-architecture.7.xml | 39 +++++++++++++++++++++++++++++++++ ovn/ovn-nb.ovsschema | 7 +++--- ovn/ovn-nb.xml | 6 +++++ ovn/ovn-sb.ovsschema | 7 +++--- ovn/ovn-sb.xml | 6 +++++ 9 files changed, 101 insertions(+), 14 deletions(-) diff --git a/ovn/controller/encaps.c b/ovn/controller/encaps.c index fde017586..2169920ba 100644 --- a/ovn/controller/encaps.c +++ b/ovn/controller/encaps.c @@ -79,8 +79,9 @@ tunnel_create_name(struct tunnel_ctx *tc, const char *chassis_id) } static void -tunnel_add(struct tunnel_ctx *tc, const char *new_chassis_id, - const struct sbrec_encap *encap) +tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg, + const char *new_chassis_id, const struct sbrec_encap *encap, + const char *local_ip) { struct smap options = SMAP_INITIALIZER(&options); smap_add(&options, "remote_ip", encap->ip); @@ -90,6 +91,16 @@ tunnel_add(struct tunnel_ctx *tc, const char *new_chassis_id, smap_add(&options, "csum", csum); } + /* Add auth info if ipsec is enabled. */ + if (sbg->ipsec) { + smap_add(&options, "remote_name", new_chassis_id); + if (local_ip) { + smap_add(&options, "local_ip", local_ip); + } else { + VLOG_INFO("Need to specify encap ip for IPsec tunnels."); + } + } + /* If there's an existing chassis record that does not need any change, * keep it. Otherwise, create a new record (if there was an existing * record, the new record will supplant it and encaps_run() will delete @@ -157,7 +168,9 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, const struct ovsrec_bridge_table *bridge_table, const struct ovsrec_bridge *br_int, const struct sbrec_chassis_table *chassis_table, - const char *chassis_id) + const char *chassis_id, + const struct sbrec_sb_global *sbg, + const struct ovsrec_open_vswitch_table *ovs_table) { if (!ovs_idl_txn || !br_int) { return; @@ -201,6 +214,16 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, } } + /* Get IP address of local chassis. */ + const char *chassis_ip; + const struct ovsrec_open_vswitch *cfg; + cfg = ovsrec_open_vswitch_table_first(ovs_table); + if (cfg) { + chassis_ip = smap_get(&cfg->external_ids, "ovn-encap-ip"); + } else { + chassis_ip = NULL; + } + SBREC_CHASSIS_TABLE_FOR_EACH (chassis_rec, chassis_table) { if (strcmp(chassis_rec->name, chassis_id)) { /* Create tunnels to the other chassis. */ @@ -209,7 +232,7 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, VLOG_INFO("No supported encaps for '%s'", chassis_rec->name); continue; } - tunnel_add(&tc, chassis_rec->name, encap); + tunnel_add(&tc, sbg, chassis_rec->name, encap, chassis_ip); } } diff --git a/ovn/controller/encaps.h b/ovn/controller/encaps.h index 054bdfa78..680b62df7 100644 --- a/ovn/controller/encaps.h +++ b/ovn/controller/encaps.h @@ -23,13 +23,18 @@ struct ovsdb_idl_txn; struct ovsrec_bridge; struct ovsrec_bridge_table; struct sbrec_chassis_table; +struct sbrec_sb_global; +struct ovsrec_open_vswitch_table; void encaps_register_ovs_idl(struct ovsdb_idl *); void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, const struct ovsrec_bridge_table *, const struct ovsrec_bridge *br_int, const struct sbrec_chassis_table *, - const char *chassis_id); + const char *chassis_id, + const struct sbrec_sb_global *, + const struct ovsrec_open_vswitch_table *); + bool encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn, const struct ovsrec_bridge *br_int); diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c index 70db66bf6..9c71425a0 100644 --- a/ovn/controller/ovn-controller.c +++ b/ovn/controller/ovn-controller.c @@ -698,7 +698,9 @@ main(int argc, char *argv[]) chassis_id, br_int); encaps_run(ovs_idl_txn, ovsrec_bridge_table_get(ovs_idl_loop.idl), br_int, - sbrec_chassis_table_get(ovnsb_idl_loop.idl), chassis_id); + sbrec_chassis_table_get(ovnsb_idl_loop.idl), chassis_id, + sbrec_sb_global_first(ovnsb_idl_loop.idl), + ovsrec_open_vswitch_table_get(ovs_idl_loop.idl)); bfd_calculate_active_tunnels(br_int, &active_tunnels); binding_run(ovnsb_idl_txn, ovs_idl_txn, sbrec_chassis_by_name, sbrec_datapath_binding_by_key, diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 067d52d82..df90d05e0 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -7064,8 +7064,8 @@ ovnnb_db_run(struct northd_context *ctx, } hmap_destroy(&ports); - /* Copy nb_cfg from northbound to southbound database. - * + /* Sync ipsec configuration. + * Copy nb_cfg from northbound to southbound database. * Also set up to update sb_cfg once our southbound transaction commits. */ const struct nbrec_nb_global *nb = nbrec_nb_global_first(ctx->ovnnb_idl); if (!nb) { @@ -7075,6 +7075,9 @@ ovnnb_db_run(struct northd_context *ctx, if (!sb) { sb = sbrec_sb_global_insert(ctx->ovnsb_txn); } + if (nb->ipsec != sb->ipsec) { + sbrec_sb_global_set_ipsec(sb, nb->ipsec); + } sbrec_sb_global_set_nb_cfg(sb, nb->nb_cfg); sb_loop->next_cfg = nb->nb_cfg; @@ -7578,6 +7581,7 @@ main(int argc, char *argv[]) ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_sb_global); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_sb_global_col_nb_cfg); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_sb_global_col_ipsec); ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_logical_flow); add_column_noalert(ovnsb_idl_loop.idl, diff --git a/ovn/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml index ae5ca8e4a..55a5db9ea 100644 --- a/ovn/ovn-architecture.7.xml +++ b/ovn/ovn-architecture.7.xml @@ -1621,6 +1621,45 @@ +

    Encrypt Tunnel Traffic with IPsec

    +

    + OVN tunnel traffic goes through physical routers and switches. These + physical devices could be untrusted (devices in public network) or might be + compromised. Enabling encryption to the tunnel traffic can prevent the + traffic data from being monitored and manipulated. +

    +

    + The tunnel traffic is encrypted with IPsec. The CMS sets the + ipsec column in the northbound NB_Global table to + enable or disable IPsec encrytion. If ipsec is true, all OVN + tunnels will be encrypted. If ipsec is false, no OVN tunnels + will be encrypted. +

    +

    + When CMS updates the ipsec column in the northbound + NB_Global table, ovn-northd copies the value to + the ipsec column in the southbound SB_Global + table. ovn-controller in each chassis monitors the southbound + database and sets the options of the OVS tunnel interface accordingly. OVS + tunnel interface options are monitored by the + ovs-monitor-ipsec daemon which configures IKE daemon to set up + IPsec connections. +

    +

    + Chassis authenticates each other by using certificate. The authentication + succeeds if the other end in tunnel presents a certificate signed by a + trusted CA and the common name (CN) matches the expected chassis name. The + SSL certificates used in role-based access controls (RBAC) can be used in + IPsec. Or use ovs-pki to create different certificates. The + certificate is required to be x.509 version 3, and with CN field and + subjectAltName field being set to the chassis name. +

    +

    + The CA certificate, chassis certificate and private key are required to be + installed in each chassis before enabling IPsec. Please see + ovs-vswitchd.conf.db(5) for setting up CA based IPsec + authentication. +

    Design Decisions

    Tunnel Encapsulations

    diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index 3e7164baa..3ee288935 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.13.0", - "cksum": "1278623084 20312", + "version": "5.14.0", + "cksum": "483549684 20358", "tables": { "NB_Global": { "columns": { @@ -19,7 +19,8 @@ "ssl": { "type": {"key": {"type": "uuid", "refTable": "SSL"}, - "min": 0, "max": 1}}}, + "min": 0, "max": 1}}, + "ipsec": {"type": "boolean"}}, "maxRows": 1, "isRoot": true}, "Logical_Switch": { diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index bc60a25dd..8c2d736d2 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -80,6 +80,12 @@ Global SSL configuration.
    + + + Tunnel encryption configuration. If this column is set to be true, all + OVN tunnels will be encrypted with IPsec. + +
diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema index ad6ad3b71..669b034e1 100644 --- a/ovn/ovn-sb.ovsschema +++ b/ovn/ovn-sb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "1.16.0", - "cksum": "3046632234 14844", + "version": "1.17.0", + "cksum": "3604753998 14890", "tables": { "SB_Global": { "columns": { @@ -17,7 +17,8 @@ "ssl": { "type": {"key": {"type": "uuid", "refTable": "SSL"}, - "min": 0, "max": 1}}}, + "min": 0, "max": 1}}, + "ipsec": {"type": "boolean"}}, "maxRows": 1, "isRoot": true}, "Chassis": { diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index 68e31db31..6d4b662b9 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -174,6 +174,12 @@ Global SSL configuration. + + + Tunnel encryption configuration. If this column is set to be true, all + OVN tunnels will be encrypted with IPsec. + +
From patchwork Tue Aug 7 16:42:45 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 954584 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="EL67Ta2D"; dkim-atps=neutral Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 41lL4t2dWbz9ryt for ; Wed, 8 Aug 2018 02:46:22 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 55566D35; Tue, 7 Aug 2018 16:43:38 +0000 (UTC) X-Original-To: ovs-dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id AE3ADD30 for ; Tue, 7 Aug 2018 16:43:37 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pl0-f53.google.com (mail-pl0-f53.google.com [209.85.160.53]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id BC4B17AA for ; Tue, 7 Aug 2018 16:43:36 +0000 (UTC) Received: by mail-pl0-f53.google.com with SMTP id w19-v6so5698244ply.8 for ; Tue, 07 Aug 2018 09:43:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=XxVizJLfTrpQ7N6Ch2JnZlXnTcwKMAkEMeaZPNAtLas=; b=EL67Ta2DVkmsZb64j3dqag1aQPVUJkpeHdap4nnK1Gl839wC5+2ZTMIYHog/IllObe +aCVEDm6Zqlti1GYuVRDOq7k7u4UFgjTbPQ2RIh1Dii4QwJmxAEaFmXKZxLAtNWgpw/H 8tSz+oQ63Gr1pBHklapR45ODpDBLXplKQeKqDK9EqyS4hMyTsO/zQjFp2W0TRvFfGfSx NIpdcV53qk3iECLwhhEhS5NtsM27RsO2FsRCJw+RpwkU+rWPeT8ItkBi1FJ7sQzsQ4e0 VXuPnBhdnKk3CSSoesOn7FtX8RQrLxkC817RypjiyRIBnTgZqwDY37sWtUhQHl++Igdi BEZg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=XxVizJLfTrpQ7N6Ch2JnZlXnTcwKMAkEMeaZPNAtLas=; b=g2yXz7/x/6T6BP0XzAuIkbtoT4TSPUb0ESSjPowuIBCvo2OwOh/EmmnrAPmFwn7NFU Bd0qosnxxJ9DvBXpSQsFabHUpqLpp0gFq93vrPpgr0Ti21BLfdH+kz3kx/8C8EtlBQDF zK6brZvH5zMmDLQ3jpPdl1XwRD6tEmSy3msMT6N96rnHAL6tYi5tXHA7x1F08XWwPxiV jrpLuKFq1u8Ug6tziLbpked3sw1xlfwPEF2ASx34ESJAWXhjKBp7Kg91dwUEJJvbslh+ 7sCgFM8qN+75WE7C9OoJdJ4gCRgBAvqg+ruzO5AXCNLdvbWByVPDoXf43GyOIncZOz0i JUdw== X-Gm-Message-State: AOUpUlHDoyJ8SRVvwdlkwM/945e+fXD10//If+8a/VvFZXuxmAYIlSuq eqmqtRKQ2oNwjVmWlTlrC9bpC3pj X-Google-Smtp-Source: AAOMgpdyVFNqI7l/OLyFvSJ5WwK3EWmXmuTjydD1SQGBy7I661JT4XdkXvx7rrgxd9qILMvws0JdPw== X-Received: by 2002:a17:902:6b89:: with SMTP id p9-v6mr18601565plk.272.1533660216026; Tue, 07 Aug 2018 09:43:36 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id d19-v6sm3489256pgi.50.2018.08.07.09.43.35 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 07 Aug 2018 09:43:35 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Tue, 7 Aug 2018 09:42:45 -0700 Message-Id: <20180807164245.18639-7-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> References: <20180807164245.18639-1-qiuyu.xiao.qyx@gmail.com> X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, FREEMAIL_FROM, RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [PATCH v5 6/6] Documentation: OVN RBAC and IPsec tutorial X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org This patch adds step-by-step guide for configuring OVN Role-Based Access Control and IPsec. Signed-off-by: Qiuyu Xiao --- Documentation/automake.mk | 2 + Documentation/index.rst | 4 +- Documentation/tutorials/index.rst | 2 + Documentation/tutorials/ovn-ipsec.rst | 147 ++++++++++++++++++++++++++ Documentation/tutorials/ovn-rbac.rst | 134 +++++++++++++++++++++++ 5 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 Documentation/tutorials/ovn-ipsec.rst create mode 100644 Documentation/tutorials/ovn-rbac.rst diff --git a/Documentation/automake.mk b/Documentation/automake.mk index 5401b9bad..082438e09 100644 --- a/Documentation/automake.mk +++ b/Documentation/automake.mk @@ -29,6 +29,8 @@ DOC_SOURCE = \ Documentation/tutorials/ovn-sandbox.rst \ Documentation/tutorials/ovs-conntrack.rst \ Documentation/tutorials/ipsec.rst \ + Documentation/tutorials/ovn-ipsec.rst \ + Documentation/tutorials/ovn-rbac.rst \ Documentation/topics/index.rst \ Documentation/topics/bonding.rst \ Documentation/topics/idl-compound-indexes.rst \ diff --git a/Documentation/index.rst b/Documentation/index.rst index bab5ba1f1..46261235c 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -66,7 +66,9 @@ vSwitch? Start here. :doc:`tutorials/ovn-sandbox` | :doc:`tutorials/ovn-openstack` | :doc:`tutorials/ovs-conntrack` | - :doc:`tutorials/ipsec` + :doc:`tutorials/ipsec` | + :doc:`tutorials/ovn-ipsec` | + :doc:`tutorials/ovn-rbac` Deeper Dive ----------- diff --git a/Documentation/tutorials/index.rst b/Documentation/tutorials/index.rst index b481090a0..35340ee56 100644 --- a/Documentation/tutorials/index.rst +++ b/Documentation/tutorials/index.rst @@ -44,4 +44,6 @@ vSwitch. ovs-advanced ovn-sandbox ovn-openstack + ovn-rbac + ovn-ipsec ovs-conntrack diff --git a/Documentation/tutorials/ovn-ipsec.rst b/Documentation/tutorials/ovn-ipsec.rst new file mode 100644 index 000000000..db3d5bc43 --- /dev/null +++ b/Documentation/tutorials/ovn-ipsec.rst @@ -0,0 +1,147 @@ +.. + 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. + + Convention for heading levels in Open vSwitch documentation: + + ======= Heading 0 (reserved for the title in a document) + ------- Heading 1 + ~~~~~~~ Heading 2 + +++++++ Heading 3 + ''''''' Heading 4 + + Avoid deeper levels because they do not render well. + +================== +OVN IPsec Tutorial +================== + +This document provides a step-by-step guide for encrypting tunnel traffic with +IPsec in Open Virtual Network (OVN). OVN tunnel traffic is transported by +physical routers and switches. These physical devices could be untrusted +(devices in public network) or might be compromised. Enabling IPsec encryption +for the tunnel traffic can prevent the traffic data from being monitored and +manipulated. More details about the OVN IPsec design can be found in +``ovn-architecture``\(7) manpage. + +This document assumes OVN is installed in your system and runs normally. Also, +you need to install OVS IPsec packages in each chassis (refer to +:ref:`install-ovs-ipsec`). + +Generating Certificates and Keys +-------------------------------- + +OVN chassis uses CA-signed certificate to authenticate peer chassis for +building IPsec tunnel. If you have enabled Role-Based Access Control (RBAC) in +OVN, you can use the RBAC SSL certificates and keys to set up OVN IPsec. Or you +can generate separate certificates and keys with ``ovs-pki`` (refer to +:ref:`gen-certs-keys`). + +.. note:: + + OVN IPsec requires x.509 version 3 certificate with the subjectAltName DNS + field setting the same string as the common name (CN) field. CN should be + set as the chassis name. ``ovs-pki`` in Open vSwitch 2.10.90 and later + generates such certificates. Please generate compatible certificates if you + use another PKI tool, or an older version of ``ovs-pki``, to manage + certificates. + +Configuring OVN IPsec +--------------------- + +You need to install the CA certificate, chassis certificate and private key in +each chassis. Use the following command:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/path/to/chassis-cert.pem \ + other_config:private_key=/path/to/chassis-privkey.pem \ + other_config:ca_cert=/path/to/cacert.pem + +Enabling OVN IPsec +------------------ + +To enable OVN IPsec, set ``ipsec`` column in ``NB_Global`` table of the +northbound database to true:: + + $ ovn-nbctl set nb_global . ipsec=true + +With OVN IPsec enabled, all tunnel traffic in OVN will be encrypted with IPsec. +To disable it, set ``ipsec`` column in ``NB_Global`` table of the northbound +database to false:: + + $ ovn-nbctl set nb_global . ipsec=false + +Troubleshooting +--------------- + +The ``ovs-monitor-ipsec`` daemon in each chassis manages and monitors the IPsec +tunnel state. Use the following ``ovs-appctl`` command to view +``ovs-monitor-ipsec`` internal representation of tunnel configuration:: + + $ ovs-appctl -t ovs-monitor-ipsec tunnels/show + +If there is a misconfiguration, then ``ovs-appctl`` should indicate why. +For example:: + + Interface name: ovn-host_2-0 v1 (CONFIGURED) <--- Should be set + to CONFIGURED. Otherwise, + error message will be + provided + Tunnel Type: geneve + Local IP: 1.1.1.1 + Remote IP: 2.2.2.2 + SKB mark: None + Local cert: /path/to/chassis-cert.pem + Local name: host_1 + Local key: /path/to/chassis-privkey.pem + Remote cert: None + Remote name: host_2 + CA cert: /path/to/cacert.pem + PSK: None + Ofport: 2 <--- Whether ovs-vswitchd has assigned Ofport + number to this Tunnel Port + CFM state: Disabled <--- Whether CFM declared this tunnel healthy + Kernel policies installed: + ... <--- IPsec policies for this OVS tunnel in + Linux Kernel installed by strongSwan + Kernel security associations installed: + ... <--- IPsec security associations for this OVS + tunnel in Linux Kernel installed by + strongswan + IPsec connections that are active: + ... <--- IPsec "connections" for this OVS + tunnel + +If you don't see any active connections, try to run the following command to +refresh the ``ovs-monitor-ipsec`` daemon:: + + $ ovs-appctl -t ovs-monitor-ipsec refresh + +You can also check the logs of the ``ovs-monitor-ipsec`` daemon and the IKE +daemon to locate issues. ``ovs-monitor-ipsec`` outputs log messages to +``/var/log/openvswitch/ovs-monitor-ipsec.log``. + +Bug Reporting +------------- + +If you think you may have found a bug with security implications, like + +1. IPsec protected tunnel accepted packets that came unencrypted; OR +2. IPsec protected tunnel allowed packets to leave unencrypted; + +Then report such bugs according to :doc:`/internals/security`. + +If bug does not have security implications, then report it according to +instructions in :doc:`/internals/bugs`. + +If you have suggestions to improve this tutorial, please send a email to +ovs-discuss@openvswitch.org. diff --git a/Documentation/tutorials/ovn-rbac.rst b/Documentation/tutorials/ovn-rbac.rst new file mode 100644 index 000000000..ec163e2df --- /dev/null +++ b/Documentation/tutorials/ovn-rbac.rst @@ -0,0 +1,134 @@ +.. + 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. + + Convention for heading levels in Open vSwitch documentation: + + ======= Heading 0 (reserved for the title in a document) + ------- Heading 1 + ~~~~~~~ Heading 2 + +++++++ Heading 3 + ''''''' Heading 4 + + Avoid deeper levels because they do not render well. + +============================================= +OVN Role-Based Access Control (RBAC) Tutorial +============================================= + +This document provides a step-by-step guide for setting up role-based access +control (RBAC) in OVN. In OVN, hypervisors (chassis) read and write the +southbound database to do configuration. Without restricting hypervisor's +access to the southbound database, a compromised hypervisor might disrupt the +entire OVN deployment by corrupting the database. RBAC ensures that each +hypervisor can only modify its own data and thus improves the security of OVN. +More details about the RBAC design can be found in ``ovn-architecture``\(7) +manpage. + +This document assumes OVN is installed in your system and runs normally. + +.. _gen-certs-keys: + +Generating Certificates and Keys +-------------------------------- + +In the OVN RBAC deployment, ovn-controller connects to the southbound database +via SSL connection. The southbound database uses CA-signed certificate to +authenticate ovn-controller. + +Suppose there are three machines in your deployment. `machine_1` runs +`chassis_1` and has IP address `machine_1-ip`. `machine_2` runs `chassis_2` and +has IP address `machine_2-ip`. `machine_3` hosts southbound database and has IP +address `machine_3-ip`. `machine_3` also hosts public key infrastructure (PKI). + +1. Initiate PKI. + + In `machine_3`:: + + $ ovs-pki init + +2. Generate southbound database's certificate request. Sign the certificate + request with the CA key. + + In `machine_3`:: + + $ ovs-pki req -u sbdb + $ ovs-pki sign sbdb switch + +3. Generate chassis certificate requests. Copy the certificate requests to + `machine_3`. + + In `machine_1`:: + + $ ovs-pki req -u chassis_1 + $ scp chassis_1-req.pem \ + machine_3@machine_3-ip:/path/to/chassis_1-req.pem + + In `machine_2`:: + + $ ovs-pki req -u chassis_2 + $ scp chassis_2-req.pem \ + machine_3@machine_3-ip:/path/to/chassis_2-req.pem + + .. note:: + + chassis_1 must be the same string as ``external_ids:system-id`` in the + Open_vSwitch table (the chassis name) of machine_1. Same applies for + chassis_2. + +4. Sign the chassis certificate requests with the CA key. Copy `chassis_1`'s + signed certificate and the CA certificate to `machine_1`. Copy `chassis_2`'s + signed certificate and the CA certificate to `machine_2`. + + In `machine_3`:: + + $ ovs-pki sign chassis_1 switch + $ ovs-pki sign chassis_2 switch + $ scp chassis_1-cert.pem \ + machine_1@machine_1-ip:/path/to/chassis_1-cert.pem + $ scp /var/lib/openvswitch/pki/switchca/cacert.pem \ + machine_1@machine_1-ip:/path/to/cacert.pem + $ scp chassis_2-cert.pem \ + machine_2@machine_2-ip:/path/to/chassis_2-cert.pem + $ scp /var/lib/openvswitch/pki/switchca/cacert.pem \ + machine_2@machine_2-ip:/path/to/cacert.pem + +Configuring RBAC +---------------- + +1. Set certificate, private key, and CA certificate for the southbound + database. Configure the southbound database to listen on SSL connection and + enforce role-based access control. + + In `machine_3`:: + + $ ovn-sbctl set-ssl /path/to/sbdb-privkey.pem \ + /path/to/sbdb-cert.pem /path/to/cacert.pem + $ ovn-sbctl set-connection role=ovn-controller pssl:6642 + +2. Set certificate, private key, and CA certificate for `chassis_1` and + `chassis_2`. Configure `chassis_1` and `chassis_2` to connect southbound + database via SSL. + + In `machine_1`:: + + $ ovs-vsctl set-ssl /path/to/chassis_1-privkey.pem \ + /path/to/chassis_1-cert.pem /path/to/cacert.pem + $ ovs-vsctl set open_vswitch . \ + external_ids:ovn-remote=ssl:machine_3-ip:6642 + + In `machine_2`:: + + $ ovs-vsctl set-ssl /path/to/chassis_2-privkey.pem \ + /path/to/chassis_2-cert.pem /path/to/cacert.pem + $ ovs-vsctl set open_vswitch . \ + external_ids:ovn-remote=ssl:machine_3-ip:6642