From patchwork Wed Jul 18 21:22:59 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 945923 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="BreZhytP"; 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 41W9Bc5prLz9s4w for ; Thu, 19 Jul 2018 07:24:08 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 280BECFC; Wed, 18 Jul 2018 21:23: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 AB3A4D05 for ; Wed, 18 Jul 2018 21:23:36 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pf0-f193.google.com (mail-pf0-f193.google.com [209.85.192.193]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 74E237B4 for ; Wed, 18 Jul 2018 21:23:35 +0000 (UTC) Received: by mail-pf0-f193.google.com with SMTP id l123-v6so2778352pfl.13 for ; Wed, 18 Jul 2018 14:23:35 -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=mUNQ9D4tDpDebMkpqT16C80gjyLms0t1IjQkmDydtfc=; b=BreZhytPFGmECEGJmH9p5gbodtmmkzWYNoRiZGyqM8wWQU/VosvsJIedIecBzaZQOO A1QzQ3bHRuW68zQiGSHxRZDZzaYxoSOnukoJIcjMJeLIbpIlsYDlOBRrHs/11pycelMJ /TtWQ1jDJQUwpWrcku78yTNl0isRCqYlzevc0kP7UcSUw+UVXtmITm6o4dtxwRp5hyk3 sho2xmqVT3GGmYWWj9ohl+ArFWl68uMUJF0vcYDEpruRunz2W9YuTv7ZngLhVMrhWjt6 mHuurvGogJ4WxbEoI10QQ59hp4QbEtrID7/ijnmuSgCm7Aa52hTVZ/CzuOpQ571A4/3Y +t4w== 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=mUNQ9D4tDpDebMkpqT16C80gjyLms0t1IjQkmDydtfc=; b=gk0hXD5RnJoIIUm/+/igeKTjDcC2SjgDdgPLZH14UjqVSdz91FIS7W8NPL1UdnUK6D stbjtwXAO4gjIipm9ZNTIi1HKISqsmlcYGrH4X2JjE8bY5pUi8D+OuD9B3FTMdn07aT3 /H3wtxwW3kRPVeQTaZKhs3QqlGGiwYzXYxWbVBFyX+5H6SIVGtL8C15NXHcMQy38WfSv tMYhcUE6XwYatGeMk/jBDPFDiC3WSIvxYpM/vu7djIU6fFzGg7g8NnlLQcV7ECdIyPZB rlk9wVfyYG93dNLL6bsdgBJP/vkIHz/2yAJ0ylYENyJM6VSWXYpAQ3nQUWI18PBZ+QkC IQmA== X-Gm-Message-State: AOUpUlGe4SZPJTPWxr0L1kZGniYbliyZxmuKD2HpDgLtylKKqVLKjJvB m6eEfyxKUERMkUQjPK+XEEeQyQ== X-Google-Smtp-Source: AAOMgpf8WZC5Dr5scJuAEJp7t3hZXqk44ELlhE1d1fWU/cQBVvlxUVaUiFp2g4a+TolBkE7V1KlKIg== X-Received: by 2002:a63:4e5f:: with SMTP id o31-v6mr7420222pgl.256.1531949014839; Wed, 18 Jul 2018 14:23:34 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id z123-v6sm6126277pfz.16.2018.07.18.14.23.34 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 18 Jul 2018 14:23:34 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Wed, 18 Jul 2018 14:22:59 -0700 Message-Id: <20180718212302.3080-2-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180718212302.3080-1-qiuyu.xiao.qyx@gmail.com> References: <20180718212302.3080-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 v2 1/4] datapath: add transport ports in route lookup to enable IPsec policy match. 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 tunnel traffic (geneve, stt, vxlan) to do encryption. The patch was tested for geneve, stt, and vxlan tunnel and the results show that IPsec policy can be set to only match the corresponding tunnel traffic. Signed-off-by: Qiuyu Xiao --- datapath/linux/compat/geneve.c | 29 +++++++++++++++++++---------- datapath/linux/compat/stt.c | 15 ++++++++++----- datapath/linux/compat/vxlan.c | 14 ++++++++++++-- 3 files changed, 41 insertions(+), 17 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; } diff --git a/datapath/linux/compat/stt.c b/datapath/linux/compat/stt.c index 2426223db..e689f7824 100644 --- a/datapath/linux/compat/stt.c +++ b/datapath/linux/compat/stt.c @@ -979,7 +979,8 @@ err_free_rt: static struct rtable *stt_get_rt(struct sk_buff *skb, struct net_device *dev, struct flowi4 *fl, - const struct ip_tunnel_key *key) + const struct ip_tunnel_key *key, + __be16 dport, __be16 sport) { struct net *net = dev_net(dev); @@ -990,6 +991,8 @@ static struct rtable *stt_get_rt(struct sk_buff *skb, fl->flowi4_tos = RT_TOS(key->tos); fl->flowi4_mark = skb->mark; fl->flowi4_proto = IPPROTO_TCP; + fl->fl4_dport = dport; + fl->fl4_sport = sport; return ip_route_output_key(net, fl); } @@ -1016,14 +1019,14 @@ netdev_tx_t ovs_stt_xmit(struct sk_buff *skb) tun_key = &tun_info->key; - rt = stt_get_rt(skb, dev, &fl, tun_key); + sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true); + rt = stt_get_rt(skb, dev, &fl, tun_key, dport, sport); if (IS_ERR(rt)) { err = PTR_ERR(rt); goto error; } df = tun_key->tun_flags & TUNNEL_DONT_FRAGMENT ? htons(IP_DF) : 0; - sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true); skb->ignore_df = 1; stt_xmit_skb(skb, rt, fl.saddr, tun_key->u.ipv4.dst, @@ -1825,20 +1828,22 @@ int ovs_stt_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) struct stt_dev *stt_dev = netdev_priv(dev); struct net *net = stt_dev->net; __be16 dport = stt_dev->dst_port; + __be16 sport; struct flowi4 fl4; struct rtable *rt; if (ip_tunnel_info_af(info) != AF_INET) return -EINVAL; - rt = stt_get_rt(skb, dev, &fl4, &info->key); + sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true); + rt = stt_get_rt(skb, dev, &fl4, &info->key, dport, sport); if (IS_ERR(rt)) return PTR_ERR(rt); ip_rt_put(rt); info->key.u.ipv4.src = fl4.saddr; - info->key.tp_src = udp_flow_src_port(net, skb, 1, USHRT_MAX, true); + info->key.tp_src = sport; info->key.tp_dst = dport; return 0; } diff --git a/datapath/linux/compat/vxlan.c b/datapath/linux/compat/vxlan.c index 7f5d5ce64..b850fdd44 100644 --- a/datapath/linux/compat/vxlan.c +++ b/datapath/linux/compat/vxlan.c @@ -896,6 +896,7 @@ out_free: static struct rtable *vxlan_get_route(struct vxlan_dev *vxlan, struct sk_buff *skb, int oif, u8 tos, __be32 daddr, __be32 *saddr, + __be16 dport, __be16 sport, struct dst_cache *dst_cache, const struct ip_tunnel_info *info) { @@ -918,6 +919,8 @@ static struct rtable *vxlan_get_route(struct vxlan_dev *vxlan, fl4.flowi4_proto = IPPROTO_UDP; fl4.daddr = daddr; fl4.saddr = *saddr; + fl4.fl4_dport = dport; + fl4.fl4_sport = sport; rt = ip_route_output_key(vxlan->net, &fl4); if (!IS_ERR(rt)) { @@ -934,6 +937,7 @@ static struct dst_entry *vxlan6_get_route(struct vxlan_dev *vxlan, __be32 label, const struct in6_addr *daddr, struct in6_addr *saddr, + __be16 dport, __be16 sport, struct dst_cache *dst_cache, const struct ip_tunnel_info *info) { @@ -961,6 +965,8 @@ static struct dst_entry *vxlan6_get_route(struct vxlan_dev *vxlan, fl6.flowlabel = ip6_make_flowinfo(RT_TOS(tos), label); fl6.flowi6_mark = skb->mark; fl6.flowi6_proto = IPPROTO_UDP; + fl6.fl6_dport = dport; + fl6.fl6_sport = sport; #ifdef HAVE_IPV6_DST_LOOKUP_NET err = ipv6_stub->ipv6_dst_lookup(vxlan->net, @@ -1090,6 +1096,7 @@ static void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, rdst ? rdst->remote_ifindex : 0, tos, dst->sin.sin_addr.s_addr, &src->sin.sin_addr.s_addr, + dst_port, src_port, dst_cache, info); if (IS_ERR(rt)) { netdev_dbg(dev, "no route to %pI4\n", @@ -1149,6 +1156,7 @@ static void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, rdst ? rdst->remote_ifindex : 0, tos, label, &dst->sin6.sin6_addr, &src->sin6.sin6_addr, + dst_port, src_port, dst_cache, info); if (IS_ERR(ndst)) { netdev_dbg(dev, "no route to %pI6\n", @@ -1439,7 +1447,8 @@ int ovs_vxlan_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) return -EINVAL; rt = vxlan_get_route(vxlan, skb, 0, info->key.tos, info->key.u.ipv4.dst, - &info->key.u.ipv4.src, NULL, info); + &info->key.u.ipv4.src, + dport, sport, NULL, info); if (IS_ERR(rt)) return PTR_ERR(rt); ip_rt_put(rt); @@ -1449,7 +1458,8 @@ int ovs_vxlan_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) ndst = vxlan6_get_route(vxlan, skb, 0, info->key.tos, info->key.label, &info->key.u.ipv6.dst, - &info->key.u.ipv6.src, NULL, info); + &info->key.u.ipv6.src, + dport, sport, NULL, info); if (IS_ERR(ndst)) return PTR_ERR(ndst); dst_release(ndst); From patchwork Wed Jul 18 21:23:00 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 945924 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="HjU5jwkn"; 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 41W9CF6h9vz9s1R for ; Thu, 19 Jul 2018 07:24:41 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id DE6EFD1F; Wed, 18 Jul 2018 21:23:46 +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 E8414CFD for ; Wed, 18 Jul 2018 21:23:45 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pg1-f172.google.com (mail-pg1-f172.google.com [209.85.215.172]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 273CB7AA for ; Wed, 18 Jul 2018 21:23:41 +0000 (UTC) Received: by mail-pg1-f172.google.com with SMTP id x5-v6so2560161pgp.7 for ; Wed, 18 Jul 2018 14:23:41 -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=lbJJm8LRTr+8lptAkE5mfsSOlu39aIASdFGnGKFbr/Q=; b=HjU5jwkneqAGBy8/I633zKftZaU/3nR3FKti2R0PcCJk3i80cd6Of8V8En/ME3bF3w tb0DL9NybvHT7anDKBCX0s9MNmVTe2j6GHFe3JjSUiAIYfP0MhwiXehNNvzi+mJFas4f JSR06IFy14TIYcFqAhXdS5Ku8UdVejE71mOPwnvt4s5cnd0wRg0NuFsPBxPIO1vffp5l WLrEuQ2AUV/NdG0xZSWFitE065lx41J0VhC4cxf+gTAotaJQAfkF1VfZS0oAW5oDbbhF q/lsj+SpOvdHnK4ZqZUWTB/nPXFfucVybVJT7omv3Qkb2yvd0jRuSL2m/2AFjMmBFmdX Z7dw== 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=lbJJm8LRTr+8lptAkE5mfsSOlu39aIASdFGnGKFbr/Q=; b=KkpRYODzjuSKXjVd9kDO8GqIwcCSN3ZOp6dfjV/obYnqEtgI1QQ1F4m7rABY3c7ipm WWJHMqhyK+dP1Zxg0nnJG8OIzMyhyHjrdV5FMt2Jfp5ZEW1SnNqMduJjOxmzUJfJoHDS nWXPobLQHoyKLOucVebX7t7AUfdYf1py9fCTV0Wy1ZehYOMAaqqZD7xMrDXK93GjO9MH MHpSs5n9aR/JXBi6797tmP0BHhYUouxzStb/Didjs+m9C9ooRQW9/2OcEx7Ot6aJBGH8 V8IQWABikSs1z/en9XZZkp4FuMo31BaDiw5D9CV44BDvK5eFoLXLaA1AVDKRsm/X8zVE NcJw== X-Gm-Message-State: AOUpUlEaND4ICJAbduP4cVWZ78q22a4z2eqkwKz2WNLKHCC+rAl0CJ9I Pow1JpcOWMHkO2XRitGH7udKZA== X-Google-Smtp-Source: AAOMgpfOYEfmvp6NC6lZVwtA1JadaNeCVdltO2dXSIBY22BJx4MKUVwcaKIe8CMfabEG0OOAmHvhQQ== X-Received: by 2002:a62:cc4d:: with SMTP id a74-v6mr6812215pfg.200.1531949019594; Wed, 18 Jul 2018 14:23:39 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id z123-v6sm6126277pfz.16.2018.07.18.14.23.38 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 18 Jul 2018 14:23:39 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Wed, 18 Jul 2018 14:23:00 -0700 Message-Id: <20180718212302.3080-3-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180718212302.3080-1-qiuyu.xiao.qyx@gmail.com> References: <20180718212302.3080-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 v2 2/4] 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: Ansis Atteka Signed-off-by: Qiuyu Xiao --- Documentation/automake.mk | 1 + Documentation/howto/index.rst | 1 + Documentation/howto/ipsec.rst | 256 +++++++ Makefile.am | 1 + debian/automake.mk | 3 + debian/control | 21 + debian/openvswitch-ipsec.dirs | 1 + debian/openvswitch-ipsec.init | 189 +++++ debian/openvswitch-ipsec.install | 1 + ipsec/automake.mk | 10 + ipsec/ovs-monitor-ipsec | 1152 ++++++++++++++++++++++++++++++ vswitchd/vswitch.xml | 124 +++- 12 files changed, 1751 insertions(+), 9 deletions(-) create mode 100644 Documentation/howto/ipsec.rst create mode 100644 debian/openvswitch-ipsec.dirs create mode 100644 debian/openvswitch-ipsec.init create mode 100644 debian/openvswitch-ipsec.install create mode 100644 ipsec/automake.mk create mode 100755 ipsec/ovs-monitor-ipsec diff --git a/Documentation/automake.mk b/Documentation/automake.mk index 2202df45b..3a505924a 100644 --- a/Documentation/automake.mk +++ b/Documentation/automake.mk @@ -59,6 +59,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..67f0f4a00 --- /dev/null +++ b/Documentation/howto/ipsec.rst @@ -0,0 +1,256 @@ +.. + 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 describes how to use Open vSwitch to provide IPsec security for +STT, GENEVE, GRE and VXLAN tunnels. This document assumes that you have +already installed Open vSwitch. + +Setup +----- + +Install strongSwan and openvswitch-ipsec debian packages:: + + $ apt-get install strongswan + $ dpkg -i openvswitch-ipsec__amd64.deb + + +Configuration +------------- + +The IPsec configuration is done by setting options of the tunnel interface. +ovs-monitor-ipsec configures IKE daemom accordingly based on the tunnel options. + +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 set a pre-shared key in both hosts to do authentication. This + method is easier to use but less secure:: + + % 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]) + +2) You can use the certificate of remote host to do authentication. First, + generate certificate and private key in each host. The certificate could be + self-signed. Refer to the ovs-pki(8) man page for more information regarding + certificate and key generation. Then, copy the remote certificate to the local + host and type:: + + % 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. + +3) You can also use CA certificate to do authentication. First, you need to + establish your public key infrastructure. The certificate of each host + needs to be signed by the CA. Refer to the ovs-pki(8) man page + for more information regarding PKI establishment. Then, copy the CA + certificate to the local host and type:: + + % 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 + + strongSwan extracts identity from the certificate's `subjectAltName` field, + so you have to use x509 v3 certificate. `remote_cn` is the hostname from the + `subjectAltName` field of the remote host's certificate. + +Forwarding Modes +~~~~~~~~~~~~~~~~ +There is delay between the user setting up IPsec tunnel and the IPsec tunnel +actually taking affect to encrypt packets. To offset the risk of unencrypted +packets leaking out during this period, you can choose a more secure forwarding +mode. There are three different forwarding modes: + +1) ovs-monitor-ipsec assumes that packets from all OVS tunnels by default + should be allowed to exit unencrypted unless particular tunnel is + explicitly configured with IPsec parameters:: + + % ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:remote_ip=1.2.3.4 \ + options:psk=swordfish]) + + + The problem with this mode is that there is inherent race condition + between ovs-monitor-ipsec and ovs-vswitchd daemons where ovs-vswitchd + could enable forwarding before ovs-monitor-ipsec actually had a chance + to configure IPsec policies. + This mode 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) ovs-monitor-ipsec assumes that packets marked with a given SKB mark + must be encrypted and hence should not be allowed to leave the system + unencrypted:: + + % 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:remote_ip=1.2.3.4 \ + options:psk=swordfish]) + + With this mode ovs-monitor-ipsec makes IKE daemon to install IPsec shunt + policies that serve as safety net and prevent unencrypted tunnel packets + to leave the host in case ovs-vswitchd configured datapath before + ovs-monitor-ipsec installed IPsec policies. + However, assumption here is that OpenFlow controller was careful + and installed OpenFlow rule with set SKB mark action specified in + OVSDB Open_vSwitch table before the first packet was able to leave + the OVS tunnel. + +3) ovs-monitor-ipsec assumes that packets coming from all OVS tunnels + by default need to be protected with IPsec unless the tunnel is explicitly + allowed to leave unencrypted. This is inverse behavior of the second + mode:: + + % 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:remote_ip=1.2.3.4 \ + options:psk=swordfish]) + + With this solution ovs-monitor-ipsec tells IKE daemon + to install IPsec shunt policies to match on skb mark 0 + which should be the default skb mark value for all tunnel packets + going through NetFilter/XFRM hooks. As a result IPsec assumes + that all packets coming from tunnels should be encrypted unless + OpenFlow controller explicitly set skb mark to a non-zero value. + This is the most secure mode, but at the same time the most intrusive + one, because the OpenFlow pipeline needs to be overhauled just because + now IPsec is enabled. + + Note that instead of using least significant bit of SKB mark, you could + just as well have used any other bit. + + +Troubleshooting +--------------- + +Use following ovs-apptcl command to get 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 + Strongswan connections that are active: + ... <--- strongSwan "connections" for this OVS + tunnel + +You can also check the logs of the ovs-ipsec-monitor daemon and the IKE daemon +to locate issues. + +Limitations +----------- + +There are several limitations: + +1) Some older Open vSwitch datapath kernel modules (in Linux Kernel tree) + do not support route lookups with transport L4 ports properly. In + this case Ethernet over L4 tunneling protocols (e.g. STT, GENEVE) + would not work. However, GRE would still work because it does not + have concept of L4 ports. + +2) Some strongSwan versions might not support certain features. For + example: + + a) AES GCM ciphers that improve performance. + b) xfrm_acq_expires setting in strongSwan configuration file. + This setting tells strongSwan how aggressively to retry + establishing tunnel, if peer did not respond to previous keying + request. + c) set_proto_port_transport_sa in charon configuration file that tells + Linux Kernel to filter out unexpected packets that came over IPsec + tunnel. + d) IPsec transport mode for NAT deployment. + +3) Since IPsec decryption is done from the software when packet has already + gotten past NIC hardware, then benefits of the most tunneling offloads, + like GENEVE or TSO for STT can't be leveraged unless NIC has IPsec offloads + as well and the NIC knows how to leverage them. + +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`. + +There is also a possibility that there is a bug in strongSwan. In that +case report it to strongSwan mailing list. diff --git a/Makefile.am b/Makefile.am index e02799a90..c0fef11fd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -480,6 +480,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/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 a4c031d85..9443e91c9 100644 --- a/debian/control +++ b/debian/control @@ -320,3 +320,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..1a9683785 --- /dev/null +++ b/debian/openvswitch-ipsec.init @@ -0,0 +1,189 @@ +#!/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 + +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() { + if [ ! -d /var/run/openvswitch ]; then + install -d -m 755 -o root -g root /var/run/openvswitch + fi + + /usr/share/openvswitch/scripts/ovs-monitor-ipsec \ + --pidfile=$PIDFILE --log-file --detach --monitor \ + unix:/var/run/openvswitch/db.sock + + return 0 +} + +stop_server() { + if [ -e $PIDFILE ]; then + kill `cat $PIDFILE` + fi + + 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 daemon" + log_warning_msg "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 \ No newline at end of file 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/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..8cb34ab9f --- /dev/null +++ b/ipsec/ovs-monitor-ipsec @@ -0,0 +1,1152 @@ +#!/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 glob +import os +import re +import subprocess +import sys +import copy +from string import Template + +from ovs.db import error +from ovs.db import types +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 not dst in policies: + policies[dst] = [] + policies[dst].append(line) + src = (a[3].split("/"))[0] + if not src 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 not remote_ip in securities: + securities[remote_ip] = [] + securities[remote_ip].append(line) + if not local_ip 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 +""" % (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, strongswan_root_prefix): + self.CHARON_CONF = strongswan_root_prefix + "/etc/strongswan.d/ovs.conf" + self.IPSEC = strongswan_root_prefix + "/usr/sbin/ipsec" + self.IPSEC_CONF = strongswan_root_prefix + "/etc/ipsec.conf" + self.IPSEC_SECRETS = strongswan_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 not ifname 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"]: + auth_section = self.auth_tmpl["pki_remote"].substitute(tunnel.conf) + 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 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 ValueError, 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, "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, 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 not ifname 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 not tunnel 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) + + seqno = idl.change_seqno # Sequence number when OVSDB was processed last time + + 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) diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml index b93da69bd..d2ac31cdb 100644 --- a/vswitchd/vswitch.xml +++ b/vswitchd/vswitch.xml @@ -473,10 +473,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 @@ -740,6 +738,87 @@ + +

+ The global configuration of IPsec tunnels is set in the + other_config column of the Open_vSwitch + table. The individual IPsec tunnel configuration is set in the + options column of the Interface table. +

+

+ ipsec_skb_mark is set to choose forwarding mode which + prevents unencrypted packets being sent out since there is delay + between the user setting up IPsec tunnel and the IPsec tunnel actually + taking affect to encrypt packets. +

+

+ There are three authentication modes: +

+
    +
  1. + To authenticate remote switch with preshared key, + private_key, certificate, and + ca_cert must not be set. psk in the + options column of the Interface table + should be set as the preshared key. +
  2. +
  3. + To authenticate remote switch with certificate, + private_key and certificate must be set. In + the CA based authentication mode, ca_cert must be set. + remote_name in the options column of the + Interface table must be set as the common name (CN) of + the remote certificate. The remote certificate must be signed by the + CA. +
  4. +
  5. + To authenticate remote switch without CA certificate, + remote_cert in the options column of the + Interface table must be set. The remote certificate can + be self-signed. +
  6. +
+ +

+ Setting ipsec_skb_mark as 1/1 blocks unecrypted packets with skb + mark set as 1. Besides, the OpenFlow rule with set SKB mark action + specified in OVSDB needs to be installed in Open_vSwitch table + before the first packet was able to leave the OVS tunnel. +

+

+ Setting ipsec_skb_mark as 0/1 blocks unecrypted packets without skb + mark. As a result, IPsec assumes that all packets coming from + tunnels should be encrypted unless OpenFlow actions explicitly + set skb mark to a non-zero value. +

+
+ +

+ 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. +

+
+
+ The overall purpose of these columns is described under Common Columns at the beginning of this document. @@ -1379,7 +1458,7 @@ - +

A port within a .

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

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

    @@ -2625,7 +2704,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. @@ -2643,8 +2722,35 @@ - + +

    + gre, geneve, vxlan, and + sttinterfaces support these options. +

    + +

    + The preshared secret to negotiate tunnel in PSK mode. This value + must match on both tunnel ends and must be unset when + remote_cert or remote_nameis set. +

    +
    + +

    + 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). It + must be unset when psk is set. +

    +
    + +

    + Common name (CN) of the remote certificate. It must be unset when + psk is set. +

    +
    + +

    Only erspan interfaces support these options. From patchwork Wed Jul 18 21:23:01 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 945925 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="P6GlXMyq"; 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 41W9Ct2SDRz9s1R for ; Thu, 19 Jul 2018 07:25:14 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id B6FAFD23; Wed, 18 Jul 2018 21:23:47 +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 DBE0FD1E for ; Wed, 18 Jul 2018 21:23:46 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pf0-f172.google.com (mail-pf0-f172.google.com [209.85.192.172]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 70F7D7CB for ; Wed, 18 Jul 2018 21:23:46 +0000 (UTC) Received: by mail-pf0-f172.google.com with SMTP id d14-v6so2795608pfo.3 for ; Wed, 18 Jul 2018 14:23:46 -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=0XYaHolWaKhnFboM0z5URTwIkKaLI+CPfD9lDqap0H8=; b=P6GlXMyqub13oyhcprsJnG4LREmFanbXPqZ3wj5cZU+UR+jPyzMe/AETpgMP5cFDrH iraJEX4rDYyj5K+/LhwYwfXpcwO7xv28lXPcvT5f3SFc0Ck2HwdqQ4w7uOKEhlFw4ajY HkgtyS6GPyM7SxXRNXvDjJE12u/tYYlHS2f52WW7Ucm0XQ3rDpr4JzQJdvfYz3yjbjiz S9AV2+HcVDG0ste3wi8RvNU9ItHhHdd6diz+GX22MyDlDOv/cdV5lVI92Any3+D47G+V i0/GpqMwI5imHl/Y5FOWglhwZ9fJBZWGk2qIMAF/t8FVD8Xawo7PAFxJB/p4gmNnHmjY XGCg== 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=0XYaHolWaKhnFboM0z5URTwIkKaLI+CPfD9lDqap0H8=; b=HHs7A39YakRqqVsWgPWKu7QLW0QaTWQLzt7Cl7gMf+ML1tcLN1jPBQXjf1XdY8doPA Ny0Pq9X7GPABR1RSqUb593Gn+Sfw/nbLVt2NOvoU5UlwrZWDEggklbx0P2TIpeJS1Znf iiRk5ohzMRls9MnMU0eDBcNf7ffNBiZ41wzILeQFi1VaTY3GvgOtdpahKx0jlDCFhUdc loBY8w7j4I+OWoZBnK1kyDm1jdZ+cIf5ib/FcocpXs590Cuy2KDmwmJzDyxnuI94FK5c VhtM/v1LU83JI/qeyf1Zd80KoCYiC0GA3p/H/AGx+7qpS6P5e7vZMdQxxarfrrnQcnRu aHJA== X-Gm-Message-State: AOUpUlGBIYks8+VBjEiE0ot/mKAuqGCdaf6AhW/MqWHLYJCzQRZ1qo2C efWS+pC6vtUx7cKSNKXNZio7+Q== X-Google-Smtp-Source: AAOMgpf54CfWYe/mUDdbsInICqs/91U6yvyJEqWYEVxPzM9+mAhWnxIdgxooyOszmI+ePk2E2r88xw== X-Received: by 2002:a63:440a:: with SMTP id r10-v6mr7209345pga.27.1531949025949; Wed, 18 Jul 2018 14:23:45 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id z123-v6sm6126277pfz.16.2018.07.18.14.23.45 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 18 Jul 2018 14:23:45 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Wed, 18 Jul 2018 14:23:01 -0700 Message-Id: <20180718212302.3080-4-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180718212302.3080-1-qiuyu.xiao.qyx@gmail.com> References: <20180718212302.3080-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 v2 3/4] ovs-pki: generate x.509 v3 certificate 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 modifies ovs-pki to generate x.509 version 3 certificate. Compared with the x.509 v1 certificate generated by ovs-pki, version 3 certificate adds subjectAltName field and sets its value the same as common name (CN). The main reason for this change is to enable strongSwan IKE daemon to extract certificate identity string from the subjectAltName field, which makes OVN IPsec implementation easier. Signed-off-by: Qiuyu Xiao --- NEWS | 3 +++ utilities/ovs-pki.in | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 7f6589a46..c30919a8d 100644 --- a/NEWS +++ b/NEWS @@ -44,6 +44,9 @@ Post-v2.9.0 both kernel datapath and userspace datapath. * Added port-based and flow-based ERSPAN tunnel port support, added OpenFlow rules matching ERSPAN fields. See ovs-fields(7). + - ovs-pki + * ovs-pki now generates x.509 version 3 certificate. The new format adds + subjectAltName field and sets its value the same as common name (CN). v2.9.0 - 19 Feb 2018 -------------------- diff --git a/utilities/ovs-pki.in b/utilities/ovs-pki.in index 4f6941865..1b6681d3a 100755 --- a/utilities/ovs-pki.in +++ b/utilities/ovs-pki.in @@ -284,7 +284,7 @@ policy = policy # default policy email_in_dn = no # Don't add the email into cert DN name_opt = ca_default # Subject name display option cert_opt = ca_default # Certificate display option -copy_extensions = none # Don't copy extensions from request +copy_extensions = copy # Copy extensions from request unique_subject = no # Allow certs with duplicate subjects # For the CA policy @@ -295,6 +295,13 @@ organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional + +# For the x509v3 extension +[ ca_cert ] +basicConstraints=CA:true + +[ usr_cert ] +basicConstraints=CA:false EOF fi @@ -307,7 +314,8 @@ EOF openssl req -config ca.cnf -nodes \ -newkey $newkey -keyout private/cakey.pem -out careq.pem \ 1>&3 2>&3 - openssl ca -config ca.cnf -create_serial -out cacert.pem \ + openssl ca -config ca.cnf -create_serial \ + -extensions ca_cert -out cacert.pem \ -days 3650 -batch -keyfile private/cakey.pem -selfsign \ -infiles careq.pem 1>&3 2>&3 chmod 0700 private/cakey.pem @@ -445,6 +453,7 @@ make_request() { [ req ] prompt = no distinguished_name = req_distinguished_name +req_extensions = v3_req [ req_distinguished_name ] C = US @@ -453,6 +462,9 @@ L = Palo Alto O = Open vSwitch OU = Open vSwitch certifier CN = $cn + +[ v3_req ] +subjectAltName = DNS:$cn EOF if test $keytype = rsa; then (umask 077 && openssl genrsa -out "$1-privkey.pem" $bits) 1>&3 2>&3 \ @@ -481,7 +493,7 @@ sign_request() { esac (cd "$pkidir/${type}ca" && - openssl ca -config ca.cnf -batch -in "$request_file") \ + openssl ca -config ca.cnf -extensions usr_cert -batch -in "$request_file") \ > "$2.tmp$$" 2>&3 mv "$2.tmp$$" "$2" } From patchwork Wed Jul 18 21:23:02 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 945926 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="dVsLumVK"; 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 41W9DQ28KCz9s4w for ; Thu, 19 Jul 2018 07:25:42 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id AB4FDD1C; Wed, 18 Jul 2018 21:23:51 +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 3CA0DD1A for ; Wed, 18 Jul 2018 21:23:51 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pg1-f194.google.com (mail-pg1-f194.google.com [209.85.215.194]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 642517AA for ; Wed, 18 Jul 2018 21:23:50 +0000 (UTC) Received: by mail-pg1-f194.google.com with SMTP id y4-v6so2562305pgp.9 for ; Wed, 18 Jul 2018 14:23:50 -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=WQ8KxftQzD6xAPP2Ff+imdqOLPUqp/gORtPRxP6BSDM=; b=dVsLumVKGPS5kXo0lmxNRjWO6otGkPU1wZrp9PzOcBq8vVOGx6rFZH/Per4oKL1EwR eaP5TWg4Fw+jFGKPpjVhnHT6xO2nDNuh9tw7+2QBj7XrqVBY9WN5hD2eQ+2FSnXKzR9O 8ybBlWoOinOR5EMN3MAGQDTztCAJVstgui3c89/UwwByuRch1WWoriNUMyiS6VxCWH1t k+vOs/X/kk5IUzzZy6H810RWJYNT4Xt+5y3NRRvFs+tWtQ/fGc7Om3l8lFK9JzMTRXT5 Npl2kGqxrvXGMbalmp+MdVGetFWzBsWdiC5tQ4EwEMsbNDenTGR2JficW7S11+LKNvAT k7TQ== 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=WQ8KxftQzD6xAPP2Ff+imdqOLPUqp/gORtPRxP6BSDM=; b=OeuCCyt5URd5oBYEPqYViH9gZ4xjzsRNtlBhGBnx+QMao540FqUQDSPxHaCzmtj+rK 2NgLgwcZWGFb4IiZ8pw2iVSWf92BufJ3jmJtQId46cbaNpXKZUZQcLlKqjyemuHoo3Lv wwxWaKq8BJWXcwOt+GSS9wFZPM4ier070Rz75yZO1BxZkAsxeqrPTN1a6M8jRYbfnDaB fY9UJ6S6RUCcJmAwLtAzGHanUIjf+exf29Ao18HmffR6cD7TtQlFGuBl3ixx32h0YHGo N8C57zmMryH24Xu1dNVRNVpfWM6ME7zFqMrAX8OXZPh0H4IZA6I6bm7qxyWn2gxZ4AXL 8bAg== X-Gm-Message-State: AOUpUlFXGjMdPyFbWnOyBIwjG+u25vM5JSFsafU+HE16OHzo0EvDhGGZ coeEiiHg5v8NGOOX+ZuAdQ+xcw== X-Google-Smtp-Source: AAOMgpeaXuzrqjdV9ELWunmdtZQSjUg9xVs3bGvmD2tTeY5ba/v7XvhFLyshlsy3V7wJgHdb7LTEJQ== X-Received: by 2002:a63:d916:: with SMTP id r22-v6mr7125654pgg.381.1531949029329; Wed, 18 Jul 2018 14:23:49 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id z123-v6sm6126277pfz.16.2018.07.18.14.23.48 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 18 Jul 2018 14:23:48 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Wed, 18 Jul 2018 14:23:02 -0700 Message-Id: <20180718212302.3080-5-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180718212302.3080-1-qiuyu.xiao.qyx@gmail.com> References: <20180718212302.3080-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 v2 4/4] 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 | 33 ++++++++++++++++++++++++---- 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, 103 insertions(+), 14 deletions(-) diff --git a/ovn/controller/encaps.c b/ovn/controller/encaps.c index fde017586..115c36411 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,17 @@ 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 +169,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 +215,17 @@ 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 +234,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 6ee72a9fa..d6dd3e268 100644 --- a/ovn/controller/ovn-controller.c +++ b/ovn/controller/ovn-controller.c @@ -679,7 +679,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 74eefc6ca..51f1671cd 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -6606,8 +6606,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) { @@ -6617,6 +6617,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; @@ -7120,6 +7123,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 8e6ddec46..528614efa 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.11.0", - "cksum": "1149260021 18713", + "version": "5.12.0", + "cksum": "3682343791 18759", "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 6aed6102a..ddee98cd1 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 9e271d433..9c5c2ef29 100644 --- a/ovn/ovn-sb.ovsschema +++ b/ovn/ovn-sb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "1.15.0", - "cksum": "1839738004 13639", + "version": "1.16.0", + "cksum": "3309568193 13685", "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 b17110e48..4090581fc 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. + +