From patchwork Fri Jul 27 20:44:29 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 950336 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="QHF5M4Um"; 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 41cgw01WpPz9ryn for ; Sat, 28 Jul 2018 06:45:36 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id E251FF43; Fri, 27 Jul 2018 20:45:02 +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 385D2F42 for ; Fri, 27 Jul 2018 20:45:01 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pl0-f65.google.com (mail-pl0-f65.google.com [209.85.160.65]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 404CD789 for ; Fri, 27 Jul 2018 20:44:59 +0000 (UTC) Received: by mail-pl0-f65.google.com with SMTP id z7-v6so2781880plo.9 for ; Fri, 27 Jul 2018 13:44:59 -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=QHF5M4UmWzSsPe2hZ0NIqOAeR5r067d89hhYXcay1ux8CNcxiqNCB7thQPZkAds5v1 qH8blZZG8Y286YEjjQ2nJUyWFy7nZX0maC70o2N4Fzyle4sw+x64YKycMxnePYiFFMWl 7J1e9tibfpQglS9Zzaoq1j+SPSBhUmLLs2d4t10gIheAIIgu4tNQjWW3xWwXij9satiV pWGwkrMW2eaYBYGMYkOMyxonzBgqF859mSogJiq2RGe1d0uH5vVSS6Z4oR0R1SrkjsNd s+0CtYcFSpipSDEYrE3ZXgPCBfJ/k68ewen9OPSyxs+T/ft8S/3C+dNGSKtxWUNQZSif yKcQ== 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=hPMJaE50/3TyHH2/9oaZu8qptazUZiWGA5SuDyNtnamYCCHbzWPMxCtCWWB1taOik3 NK8i/tOuvD05ztgiC7hj4UtUj0pnodFAKBxoh3K7s3E+/3cE8WHOrVeRjfmA29A+9zsL oKNFTiYX5WFIx3iesnibbkQwltuoo8QRi9lPKaEjnJ+1s0RQDWrLULXbfaFfBqgiPuwk wI1+H9PIamZwzUIzQ3Vj7/kePrQliyA857XRiLDUyop/ft8INq2E6JY6LpW9t1Ncdh6+ nBnT+wnoxONP6Wynup3hBNzGb9NMlu1BF+ZXg2MQPDicBHmEQy6qsy++uDKai+9Q+/WM SZbQ== X-Gm-Message-State: AOUpUlGdAbjHUJ6Wl2itErG2ugzWxFcKl7j+qzgHcbRVPddTYm1n1wPR TpiO/cqsOaVjO44bhB38KhF+sa+Z X-Google-Smtp-Source: AAOMgped0HTIqS96TQZsH6opKK145wyNce2eKo1i2w/gR83MMmWUIYCEKqXC22In6UQZj0FVpqW59g== X-Received: by 2002:a17:902:33c2:: with SMTP id b60-v6mr7395811plc.11.1532724298475; Fri, 27 Jul 2018 13:44:58 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id t88-v6sm15205440pfg.10.2018.07.27.13.44.57 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 27 Jul 2018 13:44:57 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Fri, 27 Jul 2018 13:44:29 -0700 Message-Id: <20180727204434.18525-2-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180727204434.18525-1-qiuyu.xiao.qyx@gmail.com> References: <20180727204434.18525-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 v3 1/6] 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 Fri Jul 27 20:44:30 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 950337 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="jDbYLK6Z"; 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 41cgwV6SL6z9ryn for ; Sat, 28 Jul 2018 06:46:02 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id BF4EAF6C; Fri, 27 Jul 2018 20:45:17 +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 C5950F4A for ; Fri, 27 Jul 2018 20:45:16 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pg1-f193.google.com (mail-pg1-f193.google.com [209.85.215.193]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 503C9711 for ; Fri, 27 Jul 2018 20:45:14 +0000 (UTC) Received: by mail-pg1-f193.google.com with SMTP id r1-v6so3866462pgp.11 for ; Fri, 27 Jul 2018 13:45:14 -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=e93OLLQZDou+XQ+FoWJ+5q5+0E+fs+3kWa7+oC69BCs=; b=jDbYLK6ZkA1qIsfal8nL1z/3LW2E+jHwccXAY7wq5QjrEtMBgP104YjdXpv3Wa+0nV S0aXuf+LnkRjyzYArcGtTeb80rz2ZRr0ZLOUXRQgmNLvXXxeE1hXLxj8LM22iZ1E1/HX QOUTyoSG565E3WHLPCdwfQVKCWaUsfmL1ouTbNqQVnZwda9gfId4Bj95oGeRb+8Wsy4U ZVlS+ZhzDIHf8eQNDG/QYex03nv+C6d9o7CgyUq5t0XSxbSOovMYi95TK/ZwK14otrCD z9WNsDQFgNKIwFIbOiWRVnMcZBX5JKP/XY1MiD09m1cTkzPRs1mvLcVF1v7NfBFA2g3k e1oA== 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=e93OLLQZDou+XQ+FoWJ+5q5+0E+fs+3kWa7+oC69BCs=; b=nN1dKcsvIeaKd71Fp65JF2BgVsNsIfrfEa2RqrHOaVs41OUzdVKU59+/5rtnWByXOb jP3HF1IOMu3c7PVUTtwT1CDGywPpeOSFZG5v0aeR8QyAbHEc39Vpuj3nvA1jOOepWqsI x2LLrpNL47hIwKoS8J9MHBxBPfDhLyGtCyUMxsPUjCLbxx85OkQXir6vPv1LRXrgVciC BZ3V6YMvuHtEP2VXTnxE7vRBCH1xw/HHWm/eMTvomOT4L0h1v7DEDfZE1laWzcyu0ecq 64x7RCpaFD3hqts2xwwRB3O/BMjpIalN8YPLT8+MCDjY7Nle7XB0ixCwy3wG+hEMrdGA fW3Q== X-Gm-Message-State: AOUpUlGSlTuw8g9W64MZPOKbJd8tAzlyE+iJ18ZI7jowo4qf5H4UDbS8 y+kc1jfVy21TU0qpuJqdSKikZ7m5 X-Google-Smtp-Source: AAOMgpe7YyCBSWP/jU+6r1XTXdtFzlW013XLBTRW8HmloOHlKt3dBZBmz9TGZjbWgZPkRWKomqGL4w== X-Received: by 2002:a63:e318:: with SMTP id f24-v6mr7263916pgh.175.1532724313155; Fri, 27 Jul 2018 13:45:13 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id t88-v6sm15205440pfg.10.2018.07.27.13.45.12 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 27 Jul 2018 13:45:12 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Fri, 27 Jul 2018 13:44:30 -0700 Message-Id: <20180727204434.18525-3-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180727204434.18525-1-qiuyu.xiao.qyx@gmail.com> References: <20180727204434.18525-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 v3 2/6] ipsec: reintroduce IPsec support for tunneling X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org This patch reintroduces ovs-monitor-ipsec daemon that was previously removed by commit 2b02d770 ("openvswitch: Allow external IPsec tunnel management.") After this patch, there are no IPsec flavored tunnels anymore. IPsec is enabled by setting up the right values in: 1. OVSDB:Interface:options column; 2. OVSDB:Open_vSwitch:other_config column; 3. OpenFlow pipeline. GRE, VXLAN, GENEVE, and STT IPsec tunnels are supported. LibreSwan and StrongSwan IKE daemons are supported. User can choose pre-shared key, self-signed peer certificate, or CA-signed certificate as authentication method. Signed-off-by: Ansis Atteka Signed-off-by: Qiuyu Xiao Co-authored-by: Ansis Atteka Co-authored-by: Qiuyu Xiao --- Makefile.am | 1 + ipsec/automake.mk | 10 + ipsec/ovs-monitor-ipsec | 1158 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 1169 insertions(+) create mode 100644 ipsec/automake.mk create mode 100755 ipsec/ovs-monitor-ipsec 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/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..faff89947 --- /dev/null +++ b/ipsec/ovs-monitor-ipsec @@ -0,0 +1,1158 @@ +#!/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 +charon.load_modular = yes +charon.plugins.gcm.load = yes +""" % (FILE_HEADER) + + CONF_HEADER = """%s +config setup + uniqueids=yes + +conn %%default + keyingtries=%%forever + type=transport + keyexchange=ikev2 + auto=route + ike=aes256gcm16-sha256-modp2048 + esp=aes256gcm16-modp2048 + +""" % (FILE_HEADER) + + CA_SECTION = """ca ca_auth + cacert=%s + +""" + + auth_tmpl = {"psk" : Template("""\ + left=$local_ip + right=$remote_ip + authby=psk"""), + "pki_remote" : Template("""\ + left=$local_ip + right=$remote_ip + leftid=$local_name + rightid=$remote_name + leftcert=$certificate + rightcert=$remote_cert"""), + "pki_ca" : Template("""\ + left=$local_ip + right=$remote_ip + leftid=$local_name + rightid=$remote_name + leftcert=$certificate""")} + + def __init__(self, root_prefix): + self.CHARON_CONF = root_prefix + "/etc/strongswan.d/ovs.conf" + self.IPSEC = root_prefix + "/usr/sbin/ipsec" + self.IPSEC_CONF = root_prefix + "/etc/ipsec.conf" + self.IPSEC_SECRETS = root_prefix + "/etc/ipsec.secrets" + self.conf_file = None + self.secrets_file = None + + def start_ike_daemon(self): + """This function starts StrongSwan.""" + f = open(self.CHARON_CONF, "w") + f.write(self.STRONGSWAN_CONF) + f.close() + + f = open(self.IPSEC_CONF, "w") + f.write(self.CONF_HEADER) + f.close() + + f = open(self.IPSEC_SECRETS, "w") + f.write(FILE_HEADER) + f.close() + + vlog.info("Starting StrongSwan") + subprocess.call([self.IPSEC, "start"]) + + def get_active_conns(self): + """This function parses output from 'ipsec status' command. + It returns dictionary where is interface name (as in OVSDB) + and is another dictionary. This another dictionary + uses strongSwan connection name as and more detailed + sample line from the parsed outpus as . """ + + conns = {} + proc = subprocess.Popen([self.IPSEC, 'status'], stdout=subprocess.PIPE) + + while True: + line = proc.stdout.readline().strip() + if line == '': + break + tunnel_name = line.split(":") + if len(tunnel_name) < 2: + continue + m = re.match(r"(.*)(-in-\d+|-out-\d+).*", tunnel_name[0]) + if not m: + continue + ifname = m.group(1) + if 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"]: + tmpl = self.auth_tmpl["pki_remote"] + auth_section = tmpl.substitute(tunnel.conf) + else: + tmpl = self.auth_tmpl["pki_ca"] + auth_section = tmpl.substitute(tunnel.conf) + + vals = tunnel.conf.copy() + vals["auth_section"] = auth_section + vals["version"] = tunnel.version + conf_text = transp_tmpl[tunnel.conf["tunnel_type"]].substitute(vals) + self.conf_file.write(conf_text) + + def config_fini(self): + self.secrets_file.close() + self.conf_file.close() + self.secrets_file = None + self.conf_file = None + + def refresh(self, monitor): + """This functions refreshes strongSwan configuration. Behind the + scenes this function calls: + 1. once "ipsec update" command that tells strongSwan to load + all new tunnels from "ipsec.conf"; and + 2. once "ipsec rereadsecrets" command that tells strongswan to load + secrets from "ipsec.conf" file + 3. for every removed tunnel "ipsec stroke down-nb " command + that removes old tunnels. + Once strongSwan vici bindings will be distributed with major + Linux distributions this function could be simplified.""" + vlog.info("Refreshing StrongSwan configuration") + subprocess.call([self.IPSEC, "update"]) + subprocess.call([self.IPSEC, "rereadsecrets"]) + # "ipsec update" command does not remove those tunnels that were + # updated or that disappeared from the ipsec.conf file. So, we have + # to manually remove them by calling "ipsec stroke down-nb " + # command. We use number to tell apart tunnels that + # were just updated. + # "ipsec down-nb" command is designed to be non-blocking (opposed + # to "ipsec down" command). This means that we should not be concerned + # about possibility of ovs-monitor-ipsec to block for each tunnel + # while strongSwan sends IKE messages over Internet. + conns_dict = self.get_active_conns() + for ifname, conns in conns_dict.iteritems(): + tunnel = monitor.tunnels.get(ifname) + for conn in conns: + # IPsec "connection" names that we choose in strongswan + # must start with Interface name + if not conn.startswith(ifname): + vlog.err("%s does not start with %s" % (conn, ifname)) + continue + + # version number should be the first integer after + # interface name in IPsec "connection" + try: + ver = int(re.findall(r'\d+', conn[len(ifname):])[0]) + except 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) + + # Sequence number when OVSDB was processed last time + seqno = idl.change_seqno + + while True: + unixctl_server.run() + if exiting: + break + + idl.run() + if seqno != idl.change_seqno: + monitor.read_ovsdb(idl.tables) + seqno = idl.change_seqno + + monitor.run() + + poller = ovs.poller.Poller() + unixctl_server.wait(poller) + idl.wait(poller) + poller.block() + + unixctl_server.close() + idl.close() + +if __name__ == '__main__': + try: + main() + except SystemExit: + # Let system.exit() calls complete normally + raise + except: + vlog.exception("traceback") + sys.exit(ovs.daemon.RESTART_EXIT_CODE) From patchwork Fri Jul 27 20:44:31 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 950338 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="b9mPdPq2"; 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 41cgxH444Fz9ryn for ; Sat, 28 Jul 2018 06:46:43 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id DAF45F77; Fri, 27 Jul 2018 20:45:20 +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 D57FFF69 for ; Fri, 27 Jul 2018 20:45:19 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pg1-f174.google.com (mail-pg1-f174.google.com [209.85.215.174]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 9C669793 for ; Fri, 27 Jul 2018 20:45:18 +0000 (UTC) Received: by mail-pg1-f174.google.com with SMTP id x5-v6so3866265pgp.7 for ; Fri, 27 Jul 2018 13:45:18 -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=1ksdLhugCemz59TDZlaro5dCM8s3gT+vwldhsNrin/o=; b=b9mPdPq2AAB0T61ao4W8ozPpAHGAdskUkSVrUQM6xeAHWtzIeGhUqjB3FSC4rFtl96 QDoyB+Ce5APgTOtLt5yiAEn5fDVsmG0HpjOaRDQkxX+l0Old3ISGZwbgRi96bQpfoq/B /88fo7jl9IDgQyIeFx3q2esDBrR1h80ARy+wgtRWi8eGJvNGI5pkuqITIo7LaK0N+6UO eZE9xP+gxteEqfMH6E6f6UK0lTpeiWtoehThuhs5DUL0tiqiJOI4jXA42/lUxSHTbAsY 0+rMnXvgB9GhMFN9+gEBDYdJ4I08JTVNEW+nHfGDYoJ0JJ7jwxHdQr1x576j8vmvIs0m eL0w== 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=1ksdLhugCemz59TDZlaro5dCM8s3gT+vwldhsNrin/o=; b=Ddv/ef40gE31XWp8GP78NsSUYecS2ORCko8AjLBl2/W0waSXxldwJyno6rVprC1lod qDb0QwwXT9d2wgPrx1h4h/4+lNi3DQd1ZNRdy6OEflNPIFzBbinjb2+yPF/CCJG8Iy3V ia56PyxTXe2f86brwXJCHmRZw3IBzvBvgpUHM+QOnH0T++OTd7CPynaCvFK4hzaExsrO +6YkbIMbF7RmkMv/l5bUIKkXsD3XG+evfeoyQzSDSo3qyynu4RG7BdA9lVHmMKcC1qhD bsupir2IVj3KM9tcGKsxYbFJbsVe0DBhDKLaAVQgVkO8HTJEasr7UY3ksXAsDe7RpNDW ka/g== X-Gm-Message-State: AOUpUlGTbUzW2iESmoQoHn96xmRUyGBbkdA4Jkle6yD2TRzlkgEs+4Fw zz8qwKgl0+eabLhXHEWp8QFJ2JrV X-Google-Smtp-Source: AAOMgpcagjOpYd4OIiCcEXgqVU1wNY++533KFbjPo3OZ7hToHUIWe6QiGZ3uqq6D1twAzvNFB/JB7A== X-Received: by 2002:a62:3d86:: with SMTP id x6-v6mr8080575pfj.192.1532724317916; Fri, 27 Jul 2018 13:45:17 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id t88-v6sm15205440pfg.10.2018.07.27.13.45.17 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 27 Jul 2018 13:45:17 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Fri, 27 Jul 2018 13:44:31 -0700 Message-Id: <20180727204434.18525-4-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180727204434.18525-1-qiuyu.xiao.qyx@gmail.com> References: <20180727204434.18525-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 v3 3/6] debian and rhel: Create IPsec package. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org Added rules and files to create debian and rpm ovs-ipsec packages. Signed-off-by: Ansis Atteka Signed-off-by: Qiuyu Xiao Co-authored-by: Ansis Atteka Co-authored-by: Qiuyu Xiao --- debian/automake.mk | 3 + debian/control | 21 ++ debian/openvswitch-ipsec.dirs | 1 + debian/openvswitch-ipsec.init | 181 ++++++++++++++++++ debian/openvswitch-ipsec.install | 1 + rhel/automake.mk | 1 + rhel/openvswitch-fedora.spec.in | 19 +- ...b_systemd_system_openvswitch-ipsec.service | 12 ++ utilities/ovs-ctl.in | 18 ++ 9 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 debian/openvswitch-ipsec.dirs create mode 100644 debian/openvswitch-ipsec.init create mode 100644 debian/openvswitch-ipsec.install create mode 100644 rhel/usr_lib_systemd_system_openvswitch-ipsec.service diff --git a/debian/automake.mk b/debian/automake.mk index 4d8e204bb..8a8d43c9f 100644 --- a/debian/automake.mk +++ b/debian/automake.mk @@ -20,6 +20,9 @@ EXTRA_DIST += \ debian/openvswitch-datapath-source.copyright \ debian/openvswitch-datapath-source.dirs \ debian/openvswitch-datapath-source.install \ + debian/openvswitch-ipsec.dirs \ + debian/openvswitch-ipsec.init \ + debian/openvswitch-ipsec.install \ debian/openvswitch-pki.dirs \ debian/openvswitch-pki.postinst \ debian/openvswitch-pki.postrm \ diff --git a/debian/control b/debian/control index 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..8488beccf --- /dev/null +++ b/debian/openvswitch-ipsec.init @@ -0,0 +1,181 @@ +#!/bin/sh +# +# Copyright (c) 2007, 2009 Javier Fernandez-Sanguino +# +# This is free software; you may redistribute it and/or modify +# it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2, +# or (at your option) any later version. +# +# This is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License with +# the Debian operating system, in /usr/share/common-licenses/GPL; if +# not, write to the Free Software Foundation, Inc., 59 Temple Place, +# Suite 330, Boston, MA 02111-1307 USA +# +### BEGIN INIT INFO +# Provides: openvswitch-ipsec +# Required-Start: $network $local_fs $remote_fs openvswitch-switch +# Required-Stop: $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Open vSwitch GRE-over-IPsec daemon +# Description: The ovs-monitor-ipsec script provides support for +# encrypting GRE tunnels with IPsec. +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +DAEMON=/usr/share/openvswitch/scripts/ovs-monitor-ipsec # Daemon's location +NAME=ovs-monitor-ipsec # Introduce the short server's name here +LOGDIR=/var/log/openvswitch # Log directory to use +DATADIR=/usr/share/openvswitch + +PIDFILE=/var/run/openvswitch/$NAME.pid + +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +DODTIME=10 # Time to wait for the server to die, in seconds + # If this value is set too low you might not + # let some servers to die gracefully and + # 'restart' will not work + +set -e + +running_pid() { +# Check if a given process pid's cmdline matches a given name + pid=$1 + name=$2 + [ -z "$pid" ] && return 1 + [ ! -d /proc/$pid ] && return 1 + cmd=`cat /proc/$pid/cmdline | tr "\000" " "|cut -d " " -f 2` + # Is this the expected server + [ "$cmd" != "$name" ] && return 1 + return 0 +} + +running() { +# Check if the process is running looking at /proc +# (works for all users) + + # No pidfile, probably no daemon present + [ ! -f "$PIDFILE" ] && return 1 + pid=`cat $PIDFILE` + running_pid $pid $DAEMON || return 1 + return 0 +} + +start_server() { + ${DATADIR}/scripts/ovs-ctl start-ovs-ipsec + return 0 +} + +stop_server() { + ${DATADIR}/scripts/ovs-ctl stop-ovs-ipsec + return 0 +} + +force_stop() { +# Force the process to die killing it manually + [ ! -e "$PIDFILE" ] && return + if running ; then + kill -15 $pid + # Is it really dead? + sleep "$DODTIME" + if running ; then + kill -9 $pid + sleep "$DODTIME" + if running ; then + echo "Cannot kill $NAME (pid=$pid)!" + exit 1 + fi + fi + fi + rm -f $PIDFILE +} + + +case "$1" in + start) + log_daemon_msg "Starting $NAME" + # Check if it's running first + if running ; then + log_progress_msg "apparently already running" + log_end_msg 0 + exit 0 + fi + if start_server && running ; then + # It's ok, the server started and is running + log_end_msg 0 + else + # Either we could not start it or it is not running + # after we did + # NOTE: Some servers might die some time after they start, + # this code does not try to detect this and might give + # a false positive (use 'status' for that) + log_end_msg 1 + fi + ;; + stop) + log_daemon_msg "Stopping $NAME" + if running ; then + # Only stop the server if we see it running + stop_server + log_end_msg $? + else + # If it's not running don't do anything + log_progress_msg "apparently not running" + log_end_msg 0 + exit 0 + fi + ;; + force-stop) + # First try to stop gracefully the program + $0 stop + if running; then + # If it's still running try to kill it more forcefully + log_daemon_msg "Stopping (force) $NAME" + force_stop + log_end_msg $? + fi + ;; + restart|force-reload) + log_daemon_msg "Restarting $NAME" + stop_server + # Wait some sensible amount, some server need this + [ -n "$DODTIME" ] && sleep $DODTIME + start_server + running + log_end_msg $? + ;; + status) + log_daemon_msg "Checking status of $NAME" + if running ; then + log_progress_msg "running" + log_end_msg 0 + else + log_progress_msg "apparently not running" + log_end_msg 1 + exit 1 + fi + ;; + # Use this if the daemon cannot reload + reload) + log_warning_msg "Reloading $NAME daemon: not implemented, as the" + log_warning_msg "deamon cannot re-read the config file (use restart)." + ;; + *) + N=/etc/init.d/openvswitch-ipsec + echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" \ + >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/openvswitch-ipsec.install b/debian/openvswitch-ipsec.install new file mode 100644 index 000000000..8fe665cb3 --- /dev/null +++ b/debian/openvswitch-ipsec.install @@ -0,0 +1 @@ +ipsec/ovs-monitor-ipsec usr/share/openvswitch/scripts diff --git a/rhel/automake.mk b/rhel/automake.mk index 137ff4a39..572afe2b8 100644 --- a/rhel/automake.mk +++ b/rhel/automake.mk @@ -35,6 +35,7 @@ EXTRA_DIST += \ rhel/usr_lib_systemd_system_ovn-controller.service \ rhel/usr_lib_systemd_system_ovn-controller-vtep.service \ rhel/usr_lib_systemd_system_ovn-northd.service \ + rhel/usr_lib_systemd_system_openvswitch-ipsec.service \ rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \ rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in index d88ed8ec4..d435a7989 100644 --- a/rhel/openvswitch-fedora.spec.in +++ b/rhel/openvswitch-fedora.spec.in @@ -208,6 +208,14 @@ Requires: openvswitch openvswitch-ovn-common %{_py2}-openvswitch %description ovn-docker Docker network plugins for OVN. +%package openvswitch-ipsec +Summary: Open vSwitch IPsec tunneling support +License: ASL 2.0 +Requires: openvswitch %{_py2}-openvswitch libreswan + +%description openvswitch-ipsec +This package provides IPsec tunneling support for OVS tunnels. + %prep %setup -q @@ -259,7 +267,8 @@ install -p -D -m 0644 \ rhel/usr_share_openvswitch_scripts_systemd_sysconfig.template \ $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/openvswitch for service in openvswitch ovsdb-server ovs-vswitchd ovs-delete-transient-ports \ - ovn-controller ovn-controller-vtep ovn-northd; do + ovn-controller ovn-controller-vtep ovn-northd \ + openvswitch-ipsec; do install -p -D -m 0644 \ rhel/usr_lib_systemd_system_${service}.service \ $RPM_BUILD_ROOT%{_unitdir}/${service}.service @@ -317,6 +326,10 @@ install -p -D -m 0755 \ rhel/usr_share_openvswitch_scripts_ovs-systemd-reload \ $RPM_BUILD_ROOT%{_datadir}/openvswitch/scripts/ovs-systemd-reload +install -m 0755 \ + ipsec/ovs-monitor-ipsec \ + $RPM_BUILD_ROOT%{_datadir}/openvswitch/scripts/ovs-monitor-ipsec + # remove unpackaged files rm -f $RPM_BUILD_ROOT%{_bindir}/ovs-parse-backtrace \ $RPM_BUILD_ROOT%{_sbindir}/ovs-vlan-bug-workaround \ @@ -638,6 +651,10 @@ fi %{_mandir}/man8/ovn-controller-vtep.8* %{_unitdir}/ovn-controller-vtep.service +%files openvswitch-ipsec +%{_datadir}/openvswitch/scripts/ovs-monitor-ipsec +%{_unitdir}/openvswitch-ipsec.service + %changelog * Wed Jan 12 2011 Ralf Spenneberg - First build on F14 diff --git a/rhel/usr_lib_systemd_system_openvswitch-ipsec.service b/rhel/usr_lib_systemd_system_openvswitch-ipsec.service new file mode 100644 index 000000000..813844e51 --- /dev/null +++ b/rhel/usr_lib_systemd_system_openvswitch-ipsec.service @@ -0,0 +1,12 @@ +[Unit] +Description=OVS IPsec daemon +Requires=openvswitch.service +After=openvswitch.service + +[Service] +Type=forking +ExecStart=/usr/share/openvswitch/scripts/ovs-ctl start-ovs-ipsec +ExecStop=/usr/share/openvswitch/scripts/ovs-ctl stop-ovs-ipsec + +[Install] +WantedBy=multi-user.target diff --git a/utilities/ovs-ctl.in b/utilities/ovs-ctl.in index 79b8fa853..23d31665a 100755 --- a/utilities/ovs-ctl.in +++ b/utilities/ovs-ctl.in @@ -250,6 +250,13 @@ start_forwarding () { return 0 } +start_ovs_ipsec () { + ${datadir}/scripts/ovs-monitor-ipsec \ + --pidfile=${rundir}/ovs-monitor-ipsec.pid \ + --log-file --detach --monitor unix:${rundir}/db.sock + return 0 +} + ## ---- ## ## stop ## ## ---- ## @@ -266,6 +273,11 @@ stop_forwarding () { fi } +stop_ovs_ipsec () { + ${bindir}/ovs-appctl -t ovs-monitor-ipsec exit + return 0 +} + ## --------------- ## ## enable-protocol ## ## --------------- ## @@ -550,6 +562,12 @@ case $command in delete-transient-ports) del_transient_ports ;; + start-ovs-ipsec) + start_ovs_ipsec + ;; + stop-ovs-ipsec) + stop_ovs_ipsec + ;; help) usage ;; From patchwork Fri Jul 27 20:44:32 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 950339 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="vKhsb8mw"; 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 41cgxm5LNcz9s29 for ; Sat, 28 Jul 2018 06:47:08 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id A0BBAF2E; Fri, 27 Jul 2018 20:45:30 +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 0BC94F2E for ; Fri, 27 Jul 2018 20:45:30 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pf1-f195.google.com (mail-pf1-f195.google.com [209.85.210.195]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 4A043799 for ; Fri, 27 Jul 2018 20:45:28 +0000 (UTC) Received: by mail-pf1-f195.google.com with SMTP id k21-v6so2107771pff.11 for ; Fri, 27 Jul 2018 13:45:28 -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=++FYHnVMc8aTnUzFL+qNk5r1x0nr5Ap1sTFUyB6/J9c=; b=vKhsb8mw8gYDAtfXkOlBVAG1jt6E0mLm3ize3LYvgWOi5nqHdFBcj2bGegnNxHD8mt yTZyl2WM9Pf8hHSpSzb1hT2GTetg7KmZZY33XNDDb0eGn4+AT2cXkWdrGAx3fejEPPg5 OkhskOD4ER1gHS8imZhA5N3js93gD5P77DABd88kns38tjrjg0Kl/P7Cf+QsS5jrJG10 qRhWeWCMJJtL8uZeiZnWNsNeAuo41RXqpF/wN3vsBmS2IS9aWRiy15gqc+PmTEbWo0Pt DHvmMBOoOKGMHZGVdcBM0xjHknR6LqIgPCEzb92Ww5Q5gtlbIQKptx9WLHP4d0XkAFkh KqcQ== 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=++FYHnVMc8aTnUzFL+qNk5r1x0nr5Ap1sTFUyB6/J9c=; b=RfZqjcr80mSOsom6u3GyFEQgIA7VeM20OO4GptU5j534S87KJNM+sR7tcydwRPyriC rhuU//ig96DJLpyWAicMSUrWjndIZkH1LlODLCKVVKYUSOTmNXDpSflc5bZ9Cfr7uQUu Ldu2DcSmmhSLUhFyIM2Tc5aDEvCSweSSTXowNM4f28QgtNQFDve7W2RwbFLjekWtXoAE E7kX2AGi/xZElGAbjXQZWUB846wvV+R4/QePAdNk2/rPIPlAIPY/IMOA8HDVsWrsaj81 w+AmdDcWvhsZBWtcfp8UZEVd5E2bP1t/VUTV3gCa53xDcQPl+o9uW/5QGBK335qamRRI VBsQ== X-Gm-Message-State: AOUpUlGQAwutcRfRVwBxG7+VtnKm38eB4NF7QkDruhITueu4ivh0u0N1 l6tH4kbuFHKeTBr3ZU5XflzYgb5l X-Google-Smtp-Source: AAOMgpdHqItsZHEVOumDqnMaw1nn+hV7a3IEQlm+TLAC+5xr+6lH0p5TpamknGyHhfzcw7GvSI+8DQ== X-Received: by 2002:a62:198e:: with SMTP id 136-v6mr8064615pfz.103.1532724326931; Fri, 27 Jul 2018 13:45:26 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id t88-v6sm15205440pfg.10.2018.07.27.13.45.25 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 27 Jul 2018 13:45:26 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Fri, 27 Jul 2018 13:44:32 -0700 Message-Id: <20180727204434.18525-5-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180727204434.18525-1-qiuyu.xiao.qyx@gmail.com> References: <20180727204434.18525-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 v3 4/6] Documentation: IPsec tunnel tutorial and documentation. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org tutorials/index.rst gives a step-by-setp guide to set up OVS IPsec tunnel. tutorials/ipsec.rst gives detailed explanation on the IPsec tunnel configuration methods and forwarding modes. Signed-off-by: Ansis Atteka Signed-off-by: Qiuyu Xiao Co-authored-by: Ansis Atteka Co-authored-by: Qiuyu Xiao --- Documentation/automake.mk | 2 + Documentation/howto/index.rst | 1 + Documentation/howto/ipsec.rst | 193 +++++++++++++++++ Documentation/tutorials/index.rst | 1 + Documentation/tutorials/ipsec.rst | 340 ++++++++++++++++++++++++++++++ vswitchd/vswitch.xml | 122 ++++++++++- 6 files changed, 650 insertions(+), 9 deletions(-) create mode 100644 Documentation/howto/ipsec.rst create mode 100644 Documentation/tutorials/ipsec.rst diff --git a/Documentation/automake.mk b/Documentation/automake.mk index 2202df45b..262fa20db 100644 --- a/Documentation/automake.mk +++ b/Documentation/automake.mk @@ -28,6 +28,7 @@ DOC_SOURCE = \ Documentation/tutorials/ovn-openstack.rst \ Documentation/tutorials/ovn-sandbox.rst \ Documentation/tutorials/ovs-conntrack.rst \ + Documentation/tutorials/ipsec.rst \ Documentation/topics/index.rst \ Documentation/topics/bonding.rst \ Documentation/topics/idl-compound-indexes.rst \ @@ -59,6 +60,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..17dead501 --- /dev/null +++ b/Documentation/howto/ipsec.rst @@ -0,0 +1,193 @@ +.. + Licensed under the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain + a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations + under the License. + + Convention for heading levels in Open vSwitch documentation: + + ======= Heading 0 (reserved for the title in a document) + ------- Heading 1 + ~~~~~~~ Heading 2 + +++++++ Heading 3 + ''''''' Heading 4 + + Avoid deeper levels because they do not render well. + +======================================= +Encrypt Open vSwitch Tunnels with IPsec +======================================= + +This document gives detailed description on the OVS IPsec tunnel and its +configuration modes. If you want to follow a step-by-step guide to run and +test IPsec tunnel, please refer to :doc:`/tutorials/ipsec`. + +Overview +-------- + +Why do encryption? +~~~~~~~~~~~~~~~~~~ + +OVS tunnel packets are transported from one machine to another. Along the path, +the packets are processed by physical routers and physical switches. There are +risks that these physical devices might read or write the contents of the +tunnel packets. IPsec encrypts IP payload and prevents the malicious party +sniffing or manipulating the tunnel traffic. + +OVS IPsec +~~~~~~~~~ + +OVS IPsec aims to provide a simple interface for user to add encryption on OVS +tunnels. It supports GRE, GENEVE, VXLAN, and STT tunnel. The IPsec +configuration is done by setting options of the tunnel interface and +other_config of Open_vSwitch. You can choose different authentication methods +and fowarding modes based on your system requirement. + +Configuration +------------- + +Authentication Methods +~~~~~~~~~~~~~~~~~~~~~~ + +Hosts of the IPsec tunnel need to authenticate each other to build a secure +channel. There are three authentication methods: + +1) You can use pre-shared key (PSK) to do authentication. In both hosts, set + the same PSK value. This PSK is like your password. You should never reveal + it to untrusted parties. This method is easier to use but less secure than + the certificate-based methods:: + + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:psk=swordfish + + .. note:: + + The local_ip field is required for the IPsec tunnel. + +2) You can use self-signed certificate to do authentication. In each host, + generate a certificate and the paired private key. Copy the certificate of + the remote host to the local host and configure the OVS as following:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/path/to/local_cert.pem \ + other_config:private_key=/path/to/priv_key.pem + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:remote_cert=/path/to/remote_cert.pem + + `local_cert.pem` is the certificate of the local host. `priv_key.pem` + is the private key of the local host. `priv_key.pem` needs to be stored in + a secure location. `remote_cert.pem` is the certificate of the remote host. + + .. note:: + + OVS IPsec requires x.509 version 3 certificate with the subjectAltName + DNS field setting the same string as the common name (CN) field. You can + follow the tutorial in :doc:`/tutorials/ipsec` and use ovs-pki(8) to + generate compatible certificate and key. + +3) You can also use CA-signed certificate to do authentication. First, you need + to create a CA certificate and sign each host certificate with the CA key + (please see :doc:`/tutorials/ipsec`). Copy the CA certificate to each + host and configure the OVS as following:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/path/to/local_cert.pem \ + other_config:private_key=/path/to/priv_key.pem \ + other_config:ca_cert=/path/to/ca_cert.pem + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:remote_name=remote_cn + + `ca_cert.pem` is the CA certificate. You need to set `remote_cn` as the + common name (CN) of the remote host's certificate so that only the + certificate with the expected CN can be trusted in this connection. It is + preferable to use this method than 2) if there are many remote hosts since + you don't have to copy every remote certificate to the local host. + + .. note:: + + When using certificate-based authentication, you should not set psk in + the interface options. When using psk-based authentication, you should + not set certificate, private_key, ca_cert, remote_cert, and remote_name. + +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 forwarding modes: + +1) The default mode allows unencrypted packets being sent out before IPsec + taking effect:: + + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:psk=swordfish + + This 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) The ipsec_skb_mark mode filters unencrypted packets by using skb mark of + tunnel packets:: + + $ ovs-vsctl set Open_vSwitch . other_config:ipsec_skb_mark=0/1 + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:psk=swordfish + + OVS IPsec filters unencrypted packets which carry the same skb mark as + `ipsec_skb_mark`. By setting the ipsec_skb_mark as 0/1, OVS IPsec prevents + all unencrypted tunnel packets leaving the host since the default skb mark + value for tunnel packets are 0. This affects all OVS tunnels including those + without IPsec being set up. You can install OpenFlow rules to whitelist + those non-IPsec tunnels by setting the skb mark of the tunnel traffic as + non-zero value. + +3) Setting `ipsec_skb_mark` as 1/1 only filters tunnel packets with skb mark + value being 1:: + + $ ovs-vsctl set Open_vSwitch . other_config:ipsec_skb_mark=1/1 + $ ovs-vsctl add-port br0 ipsec_gre0 -- \ + set interface ipsec_gre0 type=gre \ + options:local_ip=1.1.1.1 \ + options:remote_ip=2.2.2.2 \ + options:psk=swordfish + + Opposite to 2), this mode doesn't filter unencrypted tunnel packets by + default. To filter unencrypted IPsec tunnel traffic, you need to explicitly + set skb mark to a non-zero value for those tunnel traffic by installing + OpenFlow rules. + +Bug Reporting +------------- + +If you think you may have found a bug with security implications, like + +1) IPsec protected tunnel accepted packets that came unencrypted; OR +2) IPsec protected tunnel allowed packets to leave unencrypted; + +Then 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`. diff --git a/Documentation/tutorials/index.rst b/Documentation/tutorials/index.rst index ab90b7c84..b481090a0 100644 --- a/Documentation/tutorials/index.rst +++ b/Documentation/tutorials/index.rst @@ -40,6 +40,7 @@ vSwitch. :maxdepth: 2 faucet + ipsec ovs-advanced ovn-sandbox ovn-openstack diff --git a/Documentation/tutorials/ipsec.rst b/Documentation/tutorials/ipsec.rst new file mode 100644 index 000000000..3df5235cb --- /dev/null +++ b/Documentation/tutorials/ipsec.rst @@ -0,0 +1,340 @@ +.. + Licensed under the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain + a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations + under the License. + + Convention for heading levels in Open vSwitch documentation: + + ======= Heading 0 (reserved for the title in a document) + ------- Heading 1 + ~~~~~~~ Heading 2 + +++++++ Heading 3 + ''''''' Heading 4 + + Avoid deeper levels because they do not render well. + +================== +OVS IPsec Tutorial +================== + +This document provides a step-by-step guide for running IPsec tunnel in Open +vSwitch. A more detailed description on OVS IPsec tunnel and its +configuration modes can be found in :doc:`/howto/ipsec`. + +Requirements +------------ + +OVS IPsec tunnel requires Linux kernel (>= v4.13.0) and OVS out-of-tree kernel +module. The compatible IKE daemons are LibreSwan (>= v3.23) and StrongSwan +(>= v5.3.5). + +Installing OVS and IPsec Packages +--------------------------------- + +openvswitch-ipsec has .deb and .rpm packages. You should use the right package +based on your Linux distribution. This tutorial uses Ubuntu 16.04 and Fedora 27 +as examples. + +Ubuntu +~~~~~~ + +1. Follow :doc:`/intro/install/debian` to build debian packages. + + .. note:: + + If you have already installed OVS, then you only need to install + openvswitch-pki_*.deb and openvswitch-ipsec_*.deb in the following step. + If your kernel version is below v4.13.0, update your kernel to v4.13.0 or + above. + +2. Install the related packages:: + + $ apt-get install dkms strongswan + $ dpkg -i libopenvswitch_*.deb openvswitch-common_*.deb \ + openvswitch-switch_*.deb openvswitch-datapath-dkms_*.deb \ + python-openvswitch_*.deb openvswitch-pki_*.deb \ + openvswitch-ipsec_*.deb + + If the installation is successful, you should be able to see the + ovs-monitor-ipsec daemon is running in your system. + +Fedora +~~~~~~ + +1. Follow :doc:`/intro/install/fedora` to build RPM packages. + +2. Install the related packages:: + + $ dnf install python2-openvswitch libreswan \ + "kernel-devel-uname-r == $(uname -r)" + $ rpm -i openvswitch-*.rpm openvswitch-kmod-*.rpm \ + openvswitch-openvswitch-ipsec-*.rpm + +3. Install firewall rules to allow ESP traffic:: + + $ iptables -A IN_FedoraServer_allow -p esp -j ACCEPT + +4. Run the openvswitch-ipsec service:: + + $ systemctl start openvswitch-ipsec.service + + .. note:: + + The SELinux policies might prevent openvswitch-ipsec.service to access + certain resources. You can configure SELinux to remove such restrictions. + +Configuring IPsec tunnel +------------------------ + +Suppose you want to build IPsec tunnel between two hosts. `host_1`'s external +IP is `ip_1`. `host_2`'s external IP is `ip_2`. + +1. Set up OVS bridges in both hosts. + + In `host_1`:: + + $ ovs-vsctl add-br br-ipsec + $ ip addr add 192.0.0.1/24 dev br-ipsec + $ ip link set br-ipsec up + + In `host_2`:: + + $ ovs-vsctl add-br br-ipsec + $ ip addr add 192.0.0.2/24 dev br-ipsec + $ ip link set br-ipsec up + +2. Set up IPsec tunnel. + + There are three authentication methods. You can choose one to set up your + IPsec tunnel. + + a) Using pre-shared key + + In `host_1`:: + + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=ip_1 \ + options:remote_ip=ip_2 \ + options:psk=swordfish + + In `host_2`:: + + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=ip_2 \ + options:remote_ip=ip_1 \ + options:psk=swordfish + + .. note:: + + Pre-shared key (PSK) based authentication is easy to set up but less + secure compared with other authentication methods. You should use it + cautiously in production system. + + b) Using self-signed certificate + + Generate self-signed certificate in both `host_1` and `host_2`. Then copy + the certificate of `host_1` to `host_2` and the certificate of `host_2` + to `host_1`. + + In `host_1`:: + + $ ovs-pki req -u host_1 + $ ovs-pki self-sign host_1 + $ scp host_1-cert.pem host_2@ip_2:/path/to/host_1-cert.pem + + In `host_2`:: + + $ ovs-pki req -u host_2 + $ ovs-pki self-sign host_2 + $ scp host_2-cert.pem host_1@ip_1:/path/to/host_2-cert.pem + + .. note:: + + If you use StrongSwan as IKE daemon, please move the host certificates + to /etc/ipsec.d/certs/ and private key to /etc/ipsec.d/private/ so that + StrongSwan has permission to access those files. + + Configure IPsec tunnel to use self-signed certificate. + + In `host_1`:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/path/to/host_1-cert.pem \ + other_config:private_key=/path/to/host_1-privkey.pem + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=ip_1 \ + options:remote_ip=ip_2 \ + options:remote_cert=/path/to/host_2-cert.pem + + In `host_2`:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/path/to/host_2-cert.pem \ + other_config:private_key=/path/to/host_2-privkey.pem + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=ip_2 \ + options:remote_ip=ip_1 \ + options:remote_cert=/path/to/host_1-cert.pem + + .. note:: + + The security of the private key is very critical. Don't copy the + private key to unsafe place. + + c) Using CA-signed certificate + + First you need to establish a public key infrastructure (PKI). Suppose + you choose `host_1` to host PKI. + + In `host_1`:: + + $ ovs-pki init + + Generate certificate requests and copy the certificate request of + `host_2` to `host_1`. + + In `host_1`:: + + $ ovs-pki req -u host_1 + + In `host_2`:: + + $ ovs-pki req -u host_2 + $ scp host_2-req.pem host_1@ip_1:/path/to/host_2-req.pem + + Sign the certificate requests with the CA key. Copy `host_2`'s signed + certificate and the CA certificate to `host_2`. + + In `host_1`:: + + $ ovs-pki sign host_1 switch + $ ovs-pki sign host_2 switch + $ scp host_2-cert.pem host_2@ip_2:/path/to/host_2-cert.pem + $ scp /var/lib/openvswitch/pki/switchca/cacert.pem \ + host_2@ip_2:/path/to/cacert.pem + + .. note:: + + If you use StrongSwan as IKE daemon, please move the host certificates + to /etc/ipsec.d/certs/, CA certificate to /etc/ipsec.d/cacerts/, and + private key to /etc/ipsec.d/private/ so that StrongSwan has permission + to access those files. + + Configure IPsec tunnel to use CA-signed certificate. + + In `host_1`:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/path/to/host_1-cert.pem \ + other_config:private_key=/path/to/host_1-privkey.pem + other_config:ca_cert=/path/to/cacert.pem + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=ip_1 \ + options:remote_ip=ip_2 \ + options:remote_name=host_2 + + In `host_2`:: + + $ ovs-vsctl set Open_vSwitch . \ + other_config:certificate=/path/to/host_2-cert.pem \ + other_config:private_key=/path/to/host_2-privkey.pem + other_config:ca_cert=/path/to/cacert.pem + $ ovs-vsctl add-port br-ipsec tun -- \ + set interface tun type=gre \ + options:local_ip=ip_2 \ + options:remote_ip=ip_1 \ + options:remote_name=host_1 + + .. note:: + + remote_name is the common name (CN) of the signed-certificate. It + should be set correctly so that only certificate with the expected CN + can be authenticated. + +3. Test IPsec tunnel. + + Now you should have an IPsec GRE tunnel running between two hosts. To verify + it, in `host_1`:: + + $ ping 192.0.0.2 & + $ tcpdump -ni any net ip_2 + + You should be able to see that ESP packets are being sent from `host_1` to + `host_2`. + +Troubleshooting +--------------- + +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 + +If you don't see any active connections, try to run the following command to +refresh the ovs-ipsec-monitor daemon:: + + $ ovs-appctl -t ovs-monitor-ipsec refresh + +You can also check the logs of the ovs-ipsec-monitor daemon and the IKE daemon +to locate issues. The logs of the ovs-monitor-ipsec is in +/var/log/openvswitch/ovs-monitor-ipsec.log. + +Bug Reporting +------------- + +If you think you may have found a bug with security implications, like + +1. IPsec protected tunnel accepted packets that came unencrypted; OR +2. IPsec protected tunnel allowed packets to leave unencrypted; + +Then report such bugs according to :doc:`/internals/security`. + +If bug does not have security implications, then report it according to +instructions in :doc:`/internals/bugs`. + +If you have suggestions to improve this tutorial, please send a email to +ovs-discuss@openvswitch.org. diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml index b93da69bd..b33982ffc 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,85 @@ + +

+ 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. + Pre-shared key mode: 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. + Self-signed certificate mode: private_key and + certificate must be set. remote_cert in the + options column of the Interface table must + be set. The remote certificate can be self-signed. +
  4. +
  5. + CA-signed certificate mode: private_key, + certificate, and 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. +
  6. +
+ +

+ Setting ipsec_skb_mark as 1/1 blocks unecrypted packets with skb + mark setting 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 +1456,7 @@ - +

A port within a .

Most commonly, a port has exactly one ``interface,'' pointed to by its @@ -2368,9 +2445,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 +2702,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 +2720,35 @@ - + +

    + gre, geneve, vxlan, and + stt interfaces 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 Fri Jul 27 20:44:33 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 950343 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="F3e1COQp"; 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 41cgyK62thz9ryn for ; Sat, 28 Jul 2018 06:47:37 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id A7807F7F; Fri, 27 Jul 2018 20:45:35 +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 C6869F6A for ; Fri, 27 Jul 2018 20:45:34 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pl0-f46.google.com (mail-pl0-f46.google.com [209.85.160.46]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 626F2789 for ; Fri, 27 Jul 2018 20:45:34 +0000 (UTC) Received: by mail-pl0-f46.google.com with SMTP id m1-v6so2786868plt.6 for ; Fri, 27 Jul 2018 13:45:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=VtwQPOradqPh1YRn5e2sItLxCYPdEw4oOzcVGugHzrk=; b=F3e1COQpiUDSyKGFxksMUFnng7/ARiPAJhtHPiufGL+YBwoalOWrajfjUJWGOp+sBi 0ooAAnJglQYZP4Ktn/WFVgHaKZ2bdKR6hMQczU6sr+fuJ8rdhflk9QiXhHU8MQfj8CGr qJuLvan5yIRbgwyqnaBBEM29pWZQ7hmOImVRbWq/S3BRak9qK8ibrYUzGOxAs0edlfNy ppMPqXsyzcTmstgR1e6MnIXlPI68GrFr1usuDrnMeAGXYwggnQdpfU3Y9Rt8/sj/V06p gBL8K4okcUf3H5A2YnI/5ePuKhqB4LcT0A1R4eZnqfwjD5B6lUoPSUm0u/68i/0I6To/ ALJQ== 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=VtwQPOradqPh1YRn5e2sItLxCYPdEw4oOzcVGugHzrk=; b=hDT70IPTEo107JLPIWAf6TNdT5OSZS2r8und2iCpvMXEL0E+74YhFaJ5P9hkQRKJg+ nARYlgy9f7AEpH6B+L7Er2z9kigZC/9GtGn4iP0LPURPeTnBf7Y7QEl24CpiV/Se5719 7u7clgdkYXUKcHtvzhT30jAeoU322QGgOVtCr+3oGa3Ml/GLVmEVrTnqT3HiXtPVVq9U sSOaVbUNmw1DrRLXiXBa7OqoUU3BPMa8HQi0XF1X+70PstAvWECpVl31pANvVZF/QkaX 3SubXG2b9PnRGAytbzgnIJYwNpZw/CD++rVaGzI73X/flXM5eAjBZdXQRKxw49q+oQRP d6VA== X-Gm-Message-State: AOUpUlGYq0v4o5vopm8HONR78PHL5XLj6VVy8PNJTIKVJeElN/qmBUlP uC+pJIgtONNsTUcuYTP0ocTi+XX0 X-Google-Smtp-Source: AAOMgpfGogkKoHjF6Avnnfv5B36ium+ixXgVLGmEmapVSjVt1/dBLWXaDuOGNjybOIrs1l71gKV9iA== X-Received: by 2002:a17:902:301:: with SMTP id 1-v6mr7298591pld.127.1532724333764; Fri, 27 Jul 2018 13:45:33 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id t88-v6sm15205440pfg.10.2018.07.27.13.45.33 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 27 Jul 2018 13:45:33 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Fri, 27 Jul 2018 13:44:33 -0700 Message-Id: <20180727204434.18525-6-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180727204434.18525-1-qiuyu.xiao.qyx@gmail.com> References: <20180727204434.18525-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 v3 5/6] 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 | 25 +++++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 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..e0ba910f9 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" } @@ -529,11 +541,16 @@ elif test "$command" = self-sign; then must_exist "$arg1-req.pem" must_exist "$arg1-privkey.pem" must_not_exist "$arg1-cert.pem" + make_tmpdir + cat > "$TMP/v3.ext" <&3 || exit $? + -signkey "$arg1-privkey.pem" -req -days 3650 -text \ + -extfile $TMP/v3.ext) 2>&3 || exit $? # Reset the permissions on the certificate to the user's default. cat "$arg1-cert.pem.tmp" > "$arg1-cert.pem" From patchwork Fri Jul 27 20:44:34 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Qiuyu Xiao X-Patchwork-Id: 950344 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="hH5/gDfG"; 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 41cgyk0mKvz9ryn for ; Sat, 28 Jul 2018 06:47:58 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 658C3F96; Fri, 27 Jul 2018 20:45:42 +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 75035F39 for ; Fri, 27 Jul 2018 20:45:40 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pg1-f177.google.com (mail-pg1-f177.google.com [209.85.215.177]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 4DD91799 for ; Fri, 27 Jul 2018 20:45:39 +0000 (UTC) Received: by mail-pg1-f177.google.com with SMTP id r1-v6so3866986pgp.11 for ; Fri, 27 Jul 2018 13:45:39 -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=F9xNnR7fD8JWhSShY8qGPcylwnKx9TiNFkS8aKqxRlI=; b=hH5/gDfGwDdhRp+cyUreTBu5wVNck5nnevL/eCrw7GMRsJ0coZRX0N+nyI/Jv6Z9cP ODQpsMx7Og09nU4eMTjPX9JVIDhElxU3RFoKKeVQ2d2idH7u/D6Fi6jwYYbTQs+iAw4F zcLphALKiJJKGsVGt6XZz4akzNNrOj9DK5n3ZTeSuDBubu9fRthbwX20Sj1M91AZbZyE WcQHBEAvoSsMqLS5tpq0zhXltKNl7f4jb099WWQKhoq3uwRFvWJBDPBpVazBw6Hsf0r/ 0exdweVWrq4Ovq7OTGHm2po6G+4e8xS3nvsy0+oc8k+284ef79SnKkbtIxIRI3opW7a2 rP7Q== 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=F9xNnR7fD8JWhSShY8qGPcylwnKx9TiNFkS8aKqxRlI=; b=Bzj7Ogcn5hqrEkhABGVTAH2tKidICtL/XEhuqDRf89EFXEcTYIb8GAENimeGRW2Wyd AKSoey6GB5l812iu7KBBgqkEduifTxQe6sIJY5cqiYKwRM2XUgE6t6O5RT4LV1CDWmow U0DX5luF44MUp/JaCLxoQfmC+vIet097JaqZw7vXINnrR0qlTiNbTcSpIPiHMF5FmAzq 7q7NNfu6K1QemkChPwSrKQWrik35dlRcrf1oDClEj+1ZXDQ/+/XHFWjL5X+Ly8vNltTb R1KUgqmd7kHSNr438tUn5Q7fYjfvR105xYtWX7CEEvhVrax5c3fwlMHMDnMGPXMGvYsk KxkQ== X-Gm-Message-State: AOUpUlGoUY7EMRJqxPEwP9JzeKPT6YudIOrkLCl95VAfrABwqKcYiwzz MNANGWBqLxcdKb7GnmmCxKBCtgzi X-Google-Smtp-Source: AAOMgpe6jjatnO7OaTCCB5J4IRPhMpf9dRQRr9TLOYCswOShafLSIqmdv9v0QSYxqavglnBKpQkRPg== X-Received: by 2002:a62:25c5:: with SMTP id l188-v6mr8009754pfl.179.1532724338577; Fri, 27 Jul 2018 13:45:38 -0700 (PDT) Received: from vm1.eng.vmware.com ([66.170.99.1]) by smtp.gmail.com with ESMTPSA id t88-v6sm15205440pfg.10.2018.07.27.13.45.37 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 27 Jul 2018 13:45:38 -0700 (PDT) From: Qiuyu Xiao To: ovs-dev@openvswitch.org Date: Fri, 27 Jul 2018 13:44:34 -0700 Message-Id: <20180727204434.18525-7-qiuyu.xiao.qyx@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180727204434.18525-1-qiuyu.xiao.qyx@gmail.com> References: <20180727204434.18525-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 v3 6/6] OVN: native support for tunnel encryption X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org This patch adds IPsec support for OVN tunnel. Basically, OVN offers a binary option to its user for encryption configuration. If the IPsec option is turned on, all tunnels will be encrypted. Otherwise, no tunnel will be encrypted. The changes are summarized as below: 1) Added a ipsec column on the NB_Global table and SB_Global table. The value of ipsec column is propagated by ovn-northd from NB_Global to SB_Global. 2) ovn-controller monitors the ipsec column in SB_Global. If the ipsec value is true, ovn-controller sets options of the tunnel interface by specifying "options:remote_name=". If the ipsec value is false, ovn-controller removes these options. 3) ovs-monitor-ipsec daemon (https://mail.openvswitch.org/pipermail/ovs-dev/2018-June/348701.html) monitors the tunnel interface options and configures IKE daemon accordingly for IPsec encryption. Signed-off-by: Qiuyu Xiao --- ovn/controller/encaps.c | 31 ++++++++++++++++++++++---- ovn/controller/encaps.h | 7 +++++- ovn/controller/ovn-controller.c | 4 +++- ovn/northd/ovn-northd.c | 8 +++++-- ovn/ovn-architecture.7.xml | 39 +++++++++++++++++++++++++++++++++ ovn/ovn-nb.ovsschema | 7 +++--- ovn/ovn-nb.xml | 6 +++++ ovn/ovn-sb.ovsschema | 7 +++--- ovn/ovn-sb.xml | 6 +++++ 9 files changed, 101 insertions(+), 14 deletions(-) diff --git a/ovn/controller/encaps.c b/ovn/controller/encaps.c index fde017586..2169920ba 100644 --- a/ovn/controller/encaps.c +++ b/ovn/controller/encaps.c @@ -79,8 +79,9 @@ tunnel_create_name(struct tunnel_ctx *tc, const char *chassis_id) } static void -tunnel_add(struct tunnel_ctx *tc, const char *new_chassis_id, - const struct sbrec_encap *encap) +tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg, + const char *new_chassis_id, const struct sbrec_encap *encap, + const char *local_ip) { struct smap options = SMAP_INITIALIZER(&options); smap_add(&options, "remote_ip", encap->ip); @@ -90,6 +91,16 @@ tunnel_add(struct tunnel_ctx *tc, const char *new_chassis_id, smap_add(&options, "csum", csum); } + /* Add auth info if ipsec is enabled. */ + if (sbg->ipsec) { + smap_add(&options, "remote_name", new_chassis_id); + if (local_ip) { + smap_add(&options, "local_ip", local_ip); + } else { + VLOG_INFO("Need to specify encap ip for IPsec tunnels."); + } + } + /* If there's an existing chassis record that does not need any change, * keep it. Otherwise, create a new record (if there was an existing * record, the new record will supplant it and encaps_run() will delete @@ -157,7 +168,9 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, const struct ovsrec_bridge_table *bridge_table, const struct ovsrec_bridge *br_int, const struct sbrec_chassis_table *chassis_table, - const char *chassis_id) + const char *chassis_id, + const struct sbrec_sb_global *sbg, + const struct ovsrec_open_vswitch_table *ovs_table) { if (!ovs_idl_txn || !br_int) { return; @@ -201,6 +214,16 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, } } + /* Get IP address of local chassis. */ + const char *chassis_ip; + const struct ovsrec_open_vswitch *cfg; + cfg = ovsrec_open_vswitch_table_first(ovs_table); + if (cfg) { + chassis_ip = smap_get(&cfg->external_ids, "ovn-encap-ip"); + } else { + chassis_ip = NULL; + } + SBREC_CHASSIS_TABLE_FOR_EACH (chassis_rec, chassis_table) { if (strcmp(chassis_rec->name, chassis_id)) { /* Create tunnels to the other chassis. */ @@ -209,7 +232,7 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, VLOG_INFO("No supported encaps for '%s'", chassis_rec->name); continue; } - tunnel_add(&tc, chassis_rec->name, encap); + tunnel_add(&tc, sbg, chassis_rec->name, encap, chassis_ip); } } diff --git a/ovn/controller/encaps.h b/ovn/controller/encaps.h index 054bdfa78..680b62df7 100644 --- a/ovn/controller/encaps.h +++ b/ovn/controller/encaps.h @@ -23,13 +23,18 @@ struct ovsdb_idl_txn; struct ovsrec_bridge; struct ovsrec_bridge_table; struct sbrec_chassis_table; +struct sbrec_sb_global; +struct ovsrec_open_vswitch_table; void encaps_register_ovs_idl(struct ovsdb_idl *); void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, const struct ovsrec_bridge_table *, const struct ovsrec_bridge *br_int, const struct sbrec_chassis_table *, - const char *chassis_id); + const char *chassis_id, + const struct sbrec_sb_global *, + const struct ovsrec_open_vswitch_table *); + bool encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn, const struct ovsrec_bridge *br_int); diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c index 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. + +