网络自动化秘籍-四-

55 阅读40分钟

网络自动化秘籍(四)

原文:zh.annas-archive.org/md5/9FD2C03E57DE97FDB15C42452017B0E9

译者:飞龙

协议:CC BY-NC-SA 4.0

第八章:使用 Ansible 部署和操作 Azure 网络资源

在上一章中,我们探讨了如何在 AWS 云上提供网络资源以及如何使用 Ansible 作为编排引擎在 AWS 上部署这些资源。在本章中,我们将看看另一个主要的云提供商,微软及其 Azure 云服务。

Azure 在 Azure 云上提供多个网络服务,以便在 Azure 云上部署高度可扩展的云解决方案。Ansible 提供多个模块,用于与 Azure 云中的多个服务进行交互,并且是在 Azure 云上自动化部署的优秀工具。我们将探讨 Azure 中可用的基本网络构造,并概述如何使用 Ansible 中的多个模块来构建和验证 Azure 云中以下基本网络设置:

本章涵盖的主要配方如下:

  • 安装 Azure SDK

  • 构建 Ansible 清单

  • 验证您的 Azure 帐户

  • 创建资源组

  • 创建虚拟网络

  • 创建子网

  • 构建用户定义的路由

  • 部署网络安全组

  • 使用 Ansible 进行部署验证

  • 使用 Ansible 停用 Azure 资源

技术要求

要开始使用 Azure,您需要创建一个帐户。您可以在Azure.microsoft.com/en-au/free/.上设置一个免费帐户。

以下链接是本章中使用的 GitHub 代码:

github.com/PacktPublishing/Network-Automation-Cookbook/tree/master/ch8_azure

本章基于的软件版本如下:

  • 运行 CentOS 7 的 Ansible 机器

  • Ansible 2.9

  • Python 3.6.8

查看以下视频,了解代码的实际操作:

bit.ly/3esy3fS

安装 Azure SDK

在这个配方中,我们将概述如何安装所需的 Python 库,以开始使用 Ansible 与 Azure 编排系统进行交互。这一步是强制性的,因为这些 Python 库必须安装在 Ansible 控制机器上,以便所有 Ansible Azure 模块正常工作。

准备工作

您需要在机器上拥有 sudo 访问权限,以安装 Azure Python 库。您还需要安装 Python,并使用 Python PIP 软件包来安装 Azure 软件包。

操作步骤…

  1. 安装boto3软件包,如下所示:
$ sudo pip3 install 'Ansible[Azure]'
  1. 创建一个名为ch8_Azure的新文件夹,以托管本章的所有代码:
$ mkdir ch8_Azure

工作原理…

Ansible 的默认安装不包括运行 Ansible Azure 模块所需的所有 Python 模块。这就是为什么我们的第一步是安装所需的 Python 库。我们使用 Python pip 程序安装所有这些软件包。我们可以使用以下代码验证所有 Azure 模块是否已安装:

$ pip3 list | grep Azure

Azure-cli-core                 2.0.35

Azure-cli-nspkg                3.0.2

Azure-common                   1.1.11

Azure-graphrbac                0.40.0

Azure-keyvault                 1.0.0a1

 <<  ---  Output Omitted for brevity  -- >>

如前所述,需要安装多个 Python 软件包才能开始使用 Ansible 与 Azure API 进行交互。完成这一步后,我们现在已经准备好在 Azure 中构建我们的 playbooks 和基础设施了。

另请参阅…

有关如何开始使用 Ansible 与 Azure 云进行交互的更多信息,请参阅以下链接:

docs.Ansible.com/Ansible/latest/scenario_guides/guide_Azure.html

构建 Ansible 清单

在这个配方中,我们将概述如何构建一个 Ansible 清单,以描述我们将在 Azure 公共云中构建的网络基础设施设置。这是一个必要的步骤,因为我们将定义我们将在其中部署基础设施的所有地区的所有虚拟网络。

操作步骤…

  1. ch8_Azure目录中创建hosts文件,并填入以下数据:
$ cat hosts

[az_net]

eu_az_net

us_az_net

[eu]

eu_az_net

[us]

us_az_net
  1. 创建包含以下内容的Ansible.cfg文件:
$ cat Ansible.cfg

[defaults]

inventory=hosts

retry_files_enabled=False

gathering=explicit

host_key_checking=False

action_warnings=False
  1. 创建group_var文件夹和eu.ymlus.yml文件,其中包含以下代码:
$ cat group_var/eu.yml

---


region: westeurope

$ cat group_var/us.yml

---


region: eastus 

工作原理…

我们创建了主机的 Ansible 清单文件,并声明了我们将在 Azure 云中提供的不同虚拟网络。我们还创建了两个描述每个虚拟网络位置的组。

简而言之,我们创建了以下组来定义和分组我们的虚拟网络:

  • az_net:这将对我们在 Azure 云中的所有虚拟网络进行分组。

  • eu:列出欧盟地区的所有虚拟网络(将映射到 Azure 云中的特定区域,我们稍后将概述)。

  • us:列出美国地区的所有虚拟网络(将映射到 Azure 云中的特定区域,我们稍后将概述)。

我们可以使用此区域分组来指定在 Azure 云中使用此虚拟网络的确切区域。我们可以在eu.ymlus.yml文件的group_vars目录下定义名为region的变量来声明确切的区域。

我们将在随后的教程中使用此变量,以在相应的 Azure 区域部署我们的资源。

身份验证到您的 Azure 帐户

在本教程中,我们将概述如何创建所需的凭据,以便从 Ansible 对我们的 Azure 帐户进行编程身份验证。我们还将学习如何使用 Ansible Vault 来保护这些凭据。为了能够在以下教程中运行任何 Ansible 模块,这一步是必需的。

准备工作

Ansible 控制器必须具有互联网访问权限,并且必须按照上一个教程中的说明设置 Ansible 清单。执行这些步骤的用户必须具有 Azure 门户的管理访问权限,以便能够创建所需的资源,从而实现与 Azure API 的程序交互。

如何操作…

  1. 使用具有管理权限的帐户登录 Azure 门户:

portal.Azure.com/

  1. 在主页上,选择 Azure 活动目录:

  1. 从左侧面板中选择应用程序注册:

  1. 单击“新注册”选项,并提供以下信息以创建新应用程序。蓝色突出显示的选项是此处的活动选项:

  1. 单击注册按钮后,将创建新应用程序并显示其信息,如下截图所示(我们需要 client_id 和 tenant_id 数据):

  1. 在左侧面板中选择证书和密码:

  1. 单击“新客户端密码”:

  1. 为此应用程序指定密码名称,并选择其到期日期:

  1. 创建后,记下显示的秘密字符串(这是我们能够以纯文本形式看到此密码的唯一时间):

  1. 转到所有服务并选择订阅:

  1. 单击订阅名称(在我这里是免费试用):

  1. 记录订阅 ID 字符串(因为我们需要它进行身份验证),然后在左侧单击访问控制(IAM)选项卡:

  1. 单击“添加角色分配”,并将“参与者”角色分配给我们创建的 Ansible 应用程序:

  1. 在 Ansible 控制节点上,创建一个新文件,用于保存我们的 Ansible Vault 密码:
$ echo ‘AzureV@uLT2019’ > .vault_pass
  1. 使用 Ansible Vault 创建一个名为Azure_secret.yml的新文件,如下所示:
$ Ansible-vault create Azure_secret.yml --vault-password-file=.vault_pass
  1. 填充Azure_secret.yml文件,使用我们从 Azure 门户获取的client_idtenant_idsubscription_id的数据,以及我们为应用程序创建的密钥:
---


tenant_id: XXX-XXXXXXXX

client_id: XXX-XXXX

subscription_id: XXX-XXXXX

secret: XXX-XXXX

工作原理…

为了能够以编程方式访问 Azure API(这是 Ansible 与 Azure 云通信以提供资源的方式),我们需要在 Azure 帐户中创建一个称为服务主体的构造。这个服务主体类似于用户,但只能访问 Azure 帐户的 API。我们可以创建这个服务主体并将其称为 Ansible。然后我们可以在访问管理中为其分配贡献者角色,以便能够在我们的帐户中创建资源。为了使用这个服务主体对 Azure API 进行身份验证,我们需要提供以下四个组件:

  • Client_id

  • Tenant_id

  • Subscription_id

  • 服务主体密码

我们可以使用本步骤中概述的步骤在 Azure 门户中找到所有这些信息。我们可以创建一个名为Azure_secrets.yml的新文件,使用 Ansible Vault 进行加密,并将所有前述变量放入该文件中。

我们将在所有后续步骤中使用这些参数来对我们的 Azure 帐户进行身份验证并创建所需的基础设施。

另请参阅…

有关如何创建新服务主体的更多信息,请使用以下 URL:

docs.microsoft.com/en-au/Azure/active-directory/develop/howto-create-service-principal-portal

有关可以分配给用户/应用程序的 Azure 内置角色的更多信息,请使用以下 URL:

docs.microsoft.com/en-au/Azure/role-based-access-control/built-in-roles

创建资源组

在这个步骤中,我们将概述如何在 Azure 中部署资源组。资源组是 Azure 资源管理器部署模型的一部分,这是在 Azure 云中部署资源的首选方法。这是因为它允许我们将相似的资源(如 VM、VM NIC 和 VM IP 地址)在一个单一容器中进行分组,这个容器就是资源组。我们将使用资源组来部署所有相关资源。

准备工作

Ansible 控制机必须连接到互联网,并能够访问 Azure 公共 API 端点。Azure 帐户应按照前面的步骤进行配置。

操作步骤…

  1. 更新group_vars下的eu.ymlus.yml文件,使用以下数据定义资源组的名称:
$ cat group_vars/eu.yml

rg_name: "rg_{{ inventory_hostname }}"

$ cat group_vars/eu.yml

rg_name: "rg_{{ inventory_hostname }}"
  1. 创建一个名为pb_build_Azure_net.yml的新 playbook,内容如下:
---

- name: Build Azure Network Infrastructure
 hosts: all
 connection: local
 vars_files:
 - Azure_secret.yml
 tasks:
 - name: Create Resource group
 Azure_rm_resourcegroup:
 tenant: "{{ tenant_id }}"
 client_id: "{{ client_id }}"
 secret: "{{ secret }}"
 location: "{{ region }}"
 subscription_id: "{{ subscription_id }}"
 name: "{{ rg_name }}"
 state: "{{ state | default('present') }}"

工作原理…

我们在描述每个区域的 YAML 文件中声明我们将在每个区域部署的资源组的名称。我们使用rg_name参数来保存资源组的名称。我们使用Azure_rm_resourcegroup Ansible 模块来在 Azure 上创建资源组。它需要以下参数来对 Azure API 进行身份验证并部署资源组:

  • location参数,描述我们将部署此资源组的区域

  • tenantsecretclient_idsubscription_id参数,用于对我们的 Azure 帐户进行身份验证

  • name参数,即我们的资源组的名称

在我们的 playbook 中,我们使用vars_files参数读取Azure_secrets.yml文件,以捕获该文件中存储的所有参数。我们将连接设置为local,以指示 Ansible 在 Ansible 控制机上本地运行 playbook,并且不尝试 SSH 到清单中定义的主机。这是强制性的,因为所有 Azure 模块都需要从 Ansible 控制机运行,以调用 Azure 编排系统的 REST API 调用。

运行 playbook 后,我们可以在 Azure 门户上看到资源组已经配置好,如下截图所示:

另请参阅...

有关 Ansible 中 Azure 资源模块的更多信息,以及此模块支持的所有其他参数,请使用以下 URL:

docs.Ansible.com/Ansible/latest/modules/Azure_rm_resourcegroup_module.html

创建虚拟网络

Azure 云中的虚拟网络是我们的虚拟数据中心,类似于物理数据中心,它将我们的所有基础设施分组在一起。我们可以在相同区域和不同区域中拥有多个虚拟网络,并且我们可以在这些虚拟网络中部署我们的基础设施。在这个配方中,我们将概述如何在 Azure 中定义和配置虚拟网络。

准备工作

Ansible 控制机必须连接到互联网,能够访问 Azure 公共 API 端点,并且 Azure 账户应该按照前面的配方进行配置。资源组也应该按照前面的配方进行配置。

如何做...

  1. 使用group_vars下的eu.ymlus.yml文件更新虚拟网络的名称和 CIDR 地址:
$ cat group_vars/eu.yml
vnet_name: "vn_{{ inventory_hostname }}"
vnet_cidr: 10.1.0.0/16
$ cat group_vars/us.yml
vnet_name: "vn_{{ inventory_hostname }}"
vnet_cidr: 10.2.0.0/16
  1. 使用任务更新pb_build_Azure_net.yml playbook 来创建虚拟网络:
    - name: Create Virtual Networks
 Azure_rm_virtualnetwork:
 tenant: "{{ tenant_id }}"
 client_id: "{{ client_id }}"
 secret: "{{ secret }}"
 location: "{{ region }}"
 subscription_id: "{{ subscription_id }}"
 resource_group: "{{ rg_name}}"
 name: "{{ vnet_name }}"
 address_prefixes_cidr: "{{ vnet_cidr }}"
 state: "{{ state | default('present') }}"

工作原理...

为了创建虚拟网络,我们需要提供其名称,以及此虚拟网络将占用的 CIDR IP 范围。我们在区域的 YAML 文件中定义这两个参数为vnet_namevnet_cidr。我们使用Azure_rm_virtualnetwork Ansible 模块来创建所有必需的虚拟网络,并提供以下参数:

  • resource_group中的资源组名称。

  • location参数描述了我们将部署此资源组的区域。

  • name参数中的每个子网的名称,以及address_prefixes_cidr参数中的 CIDR IP 范围。

  • “租户”、“密钥”、“客户端 ID”和“订阅 ID”参数都用于对我们的 Azure 账户进行身份验证。

运行 playbook 后,我们可以看到虚拟网络已经创建,如下截图所示:

另请参阅...

有关 Ansible 中 Azure 虚拟网络模块的更多信息,以及此模块支持的所有其他参数,请使用以下 URL:

docs.Ansible.com/Ansible/latest/modules/Azure_rm_virtualnetwork_module.html

创建子网

子网是 Azure 云中用于对虚拟网络进行分段的网络构造。它用于为我们提供工具,将我们的虚拟网络分隔成不同的路由和安全域,以便我们可以控制每个子网内的不同路由和安全行为。在这个配方中,我们将概述如何在 Azure 云中定义和配置子网。

准备工作

Ansible 控制机必须连接到互联网,能够访问 Azure 公共 API 端点。Azure 账户应该按照前面的配方进行配置。资源组和虚拟网络也应该按照前面的配方进行配置。

如何做...

  1. 使用子网信息更新group_vars下的eu.ymlus.yml文件:
$ cat group_vars/eu.yml
subnets:
 - name: web_tier
 cidr: 10.1.1.0/24
 - name: db_tier
 cidr: 10.1.2.0/24

$ cat group_vars/us.yml
subnets:
 - name: web_tier
 cidr: 10.2.1.0/24
 - name: db_tier
 cidr: 10.2.2.0/24    
  1. 使用任务更新pb_build_Azure_net.yml playbook 来创建子网:
 - name: Create Subnets
 Azure_rm_subnet:
 tenant: "{{ tenant_id }}"
 client_id: "{{ client_id }}"
 secret: "{{ secret }}"
 subscription_id: "{{ subscription_id }}"
 resource_group: "{{ rg_name}}"
 name: "{{ item.name}}"
 virtual_network_name:  "{{ vnet_name }}"
 address_prefix_cidr: "{{ item.cidr }}"
 state: "{{ state | default('present') }}"
 loop: "{{ subnets }}"
 loop_control:
 label: "{{ item.name }}"

工作原理...

为了在虚拟网络中创建子网,我们需要提供虚拟网络和子网的 CIDR 前缀,该前缀必须在虚拟网络的 CIDR 内。我们在子网的数据结构中定义这些内容,其中包括我们想要配置的每个子网的名称和 CIDR。我们可以使用Azure_rm_subnet Ansible 模块来创建所有必需的子网,并且可以循环遍历子网的数据结构以提供必需的参数。

运行 playbook 后,我们可以看到在每个虚拟网络中创建的子网,如下截图所示:

另请参阅...

有关 Ansible 中 Azure 子网模块的更多信息,以及此模块支持的所有其他参数,请使用以下 URL:

docs.Ansible.com/Ansible/latest/modules/Azure_rm_subnet_module.html

构建用户定义的路由

在这个配方中,我们将概述如何使用用户定义的路由器控制子网内的路由。这个用户定义的路由对象可以与特定的子网关联。我们可以定义自定义路由来调整 Azure 云中子网内的转发行为。

准备工作

Ansible 控制机必须连接到互联网,并能够访问 Azure 公共 API 端点。Azure 帐户应按照前面的配方进行配置。资源组、虚拟网络和子网也应按照前面的配方进行配置。

如何做...

  1. group_vars下的eu.ymlus.yml文件中更新route_tables数据,如下所示:
$ cat group_vars/eu.yml  group_vars/us.yml
route_tables:
 - name: db_tier_rt
 subnet: db_tier
 routes:
 - name: Default Route
 prefix: 0.0.0.0/0
 nh: none
  1. 使用以下任务更新pb_build_Azure_net.yml playbook 以创建自定义路由表:
 - name: Create Custom Route Table
 Azure_rm_routetable:
 tenant: "{{ tenant_id }}"
 client_id: "{{ client_id }}"
 secret: "{{ secret }}"
 subscription_id: "{{ subscription_id }}"
 resource_group: "{{ rg_name}}"
 name: "{{ item.name}}"
 state: "{{ state | default('present') }}"
 loop: "{{ route_tables }}"
 tags: routing
  1. 使用以下任务更新 playbook 以在自定义路由表中配置路由:
 - name: Provision Routes
 Azure_rm_route:
 tenant: "{{ tenant_id }}"
 client_id: "{{ client_id }}"
 secret: "{{ secret }}"
 subscription_id: "{{ subscription_id }}"
 resource_group: "{{ rg_name}}"
 route_table_name: "{{ item.0.name }}"
 name: "{{ item.1.name}}"
 address_prefix: "{{ item.1.prefix }}"
 next_hop_type: "{{ item.1.nh }}"
 state: "{{ state | default('present') }}"
 with_subelements:
 - "{{ route_tables }}"
 - routes
 tags: routing
  1. 使用以下任务更新 playbook 以将自定义路由与子网关联:
 - name: Attach Route Table to Subnet
 Azure_rm_subnet:
 tenant: "{{ tenant_id }}"
 client_id: "{{ client_id }}"
 secret: "{{ secret }}"
 subscription_id: "{{ subscription_id }}"
 resource_group: "{{ rg_name}}"
 name: "{{ item.subnet}}"
 virtual_network_name:  "{{ vnet_name }}"
 route_table: "{{ item.name }}"
 state: "{{ state | default('present') }}"
 loop: "{{ route_tables }}"
 loop_control:
 label: "{{ item.name }}"
 tags: routing

工作原理...

在我们的设置中,我们有两个子网(webDB),我们需要为DB子网提供不同的路由处理,以便它不具有公共互联网访问权限。我们可以通过创建新的自定义路由表并安装next-hop设置为none的默认路由来实现这一点,以丢弃所有面向互联网的流量。

我们需要在route_tables变量中定义我们的自定义路由表,并将其包含在每个区域定义中。然后,我们可以使用Azure_rm_routetable Ansible 模块在特定资源组中创建路由表,并使用Azure_rm_route模块在每个路由表中创建所需的路由。最后,我们可以使用Azure_rm_subnet模块将路由表附加到特定子网,以修改此子网的默认路由行为。

以下截图概述了创建的新路由表:

以下截图概述了一个路由表的确切细节,自定义路由以及此自定义路由所附加的子网:

另请参阅...

有关 Ansible 中 Azure 路由表模块的更多信息,以及此模块支持的所有其他参数,请使用以下 URL:

部署网络安全组

云环境中的安全性至关重要,Azure 云提供了不同的工具和服务来帮助构建应用程序的安全云环境。在这个示例中,我们将看一下其中一个服务:网络安全组NSG)。NSG 是一个有状态的防火墙,可以附加到虚拟机或子网,以限制通过虚拟机或子网流动的流量。在这个示例中,我们将概述如何在 Azure 云上定义和配置 NSG。

准备工作

Ansible 控制机必须连接到互联网,并能够到达 Azure 公共 API 端点。Azure 帐户应按照前面的示例进行配置。资源组、虚拟网络和子网也应按照前面的示例进行配置。

操作步骤...

  1. 使用 ACL 数据更新group_vars下的eu.ymlus.yml文件,如下所示:
$ cat group_vars/eu.yml  group_vars/us.yml
acls:
 - name: Inbound_Web_Tier
 subnet: web_tier
 rules:
 - name: Allow_HTTP_Internet
 destination_address_prefix: 10.1.1.0/24
 direction: Inbound
 access: Allow
 protocol: Tcp
 destination_port_range:
 - 80
 - 443
 priority: 101
  1. 使用以下任务更新pb_build_Azure_net.yml playbook,创建安全组并填充其所有规则:
 - name: Create new Security Group
 Azure_rm_securitygroup:
 tenant: "{{ tenant_id }}"
 client_id: "{{ client_id }}"
 secret: "{{ secret }}"
 subscription_id: "{{ subscription_id }}"
 resource_group: "{{ rg_name}}"
 name: "{{ item.name }}"
 purge_rules: yes
 rules: "{{ item.rules }}"
 loop: "{{ acls }}"
 Tags: security
  1. 使用以下任务更新 playbook,将安全组与相应的子网关联起来:
 - name: Attach Security Group to Subnet
 Azure_rm_subnet:
 tenant: "{{ tenant_id }}"
 client_id: "{{ client_id }}"
 secret: "{{ secret }}"
 subscription_id: "{{ subscription_id }}"
 resource_group: "{{ rg_name}}"
 name: "{{ item.subnet}}"
 virtual_network_name:  "{{ vnet_name }}"
 security_group: "{{ item.name }}"
 state: "{{ state | default('present') }}"
 loop: "{{ acls }}"
 tags: security

工作原理...

Azure 提供了附加到子网的默认 NSG。这些为部署在这些子网中的计算资源提供基本安全控制。入站流量的默认策略包括以下默认规则:

  • 允许虚拟网络 CIDR 范围之间的入站流量(子网间通信)。

  • 允许来自 Azure 负载均衡器的入站流量。

  • 拒绝其他任何流量。

在出站方向,默认规则如下:

  • 允许虚拟网络 CIDR 之间的出站流量(子网间通信)。

  • 允许出站流量到互联网。

  • 拒绝其他任何流量。

Azure NSG 提供了一种机制,通过定义一个自定义 NSG 来增强 Azure 应用的默认 NSG,该自定义 NSG 附加到默认 NSG 上。结果 NSG 根据每个规则的优先级值进行评估(具有较低值的规则首先进行评估),一旦匹配规则,规则就适用于通过子网传输的流量。

由于我们在Web_tier子网中部署了一个 Web 应用程序,我们需要允许入站 HTTP 和 HTTPs 流量到该子网。因此,我们可以创建一个 ACL 定义来创建一个自定义 NSG,并在入站方向上定义所需的参数,以允许这些流量。

我们可以使用Azure_rm_securitygroup Ansible 模块循环遍历所有自定义 ACL,并创建 NSG 和相应的规则。我们可以使用Azure_rm_subnet将安全组附加到子网。

以下截图显示了定义的新 NSG:

以下截图显示了定义的结果 NSG 规则(包括自定义和默认)的入站和出站方向:

另请参阅...

有关 Ansible 中 Azure NSG 模块的更多信息,以及此模块支持的所有其他参数,请使用以下 URL:

docs.Ansible.com/Ansible/latest/modules/Azure_rm_securitygroup_module.html

使用 Ansible 进行部署验证

Ansible 提供了多个模块来收集在 Azure 中部署的不同资源的操作状态。我们可以使用这些模块来验证 Azure 云中我们网络的当前状态。这提供了一种编程方法来验证部署,而无需通过 GUI 登录到门户网站来检查基础设施中不同组件的状态。在这个示例中,我们将概述如何使用多个模块来验证我们已部署的资源组和虚拟网络。

准备工作

Ansible 控制机必须连接到互联网,并能够访问 Azure 公共 API 端点。Azure 帐户还应按照前面的示例进行配置。

如何操作...

  1. 创建一个新文件~/.Azure/credentials,用于存储所有连接到 Azure 的凭据,如下所示:
$ cat ~/.Azure/credentials
[default]
subscription_id=XXX-XXXX-XXXX
client_id=XXX-XXXX-XXXX
secret=XXX-XXXX-XXXX
tenant=XXX-XXXX-XXXX
  1. 创建一个新的 playbookpb_validate_Azure_net.yml,以验证我们的部署,并包括以下任务来收集资源组事实并验证它:
$ cat pb_validate_Azure_net.yml
- name: Build Azure Network Infrastructure
 hosts: all
 connection: local
 tasks:
 - name: Get Resource Facts
 Azure_rm_resourcegroup_facts:
 name: "{{ rg_name }}"
 register: rg_facts
 tags: rg_facts
 - name: Validate Resource Group is Deployed
 assert:
 that:
 - rg.name == rg_name
 - rg.properties.provisioningState == 'Succeeded'
 - rg.location == region
 loop: "{{ Azure_resourcegroups }}"
 loop_control:
 loop_var: rg
 tags: rg_facts
  1. 更新pb_validate_Azure_net.yml playbook,包括收集虚拟网络事实并验证其状态的任务:
    - name: Validate Virtual Network is Deployed
      Azure_rm_virtualnetwork_facts:
        resource_group: "{{ rg_name }}"
      register: vnet_facts
      tags: vnet_facts
    - name: Validate Virtual Networks are Deployed
      assert:
        that:
          - vnet.name == vnet_name
          - vnet.properties.provisioningState == 'Succeeded'
          - vnet.properties.addressSpace.addressPrefixes | length == 1
          - vnet.properties.addressSpace.addressPrefixes[0] == vnet_cidr
      loop: "{{ Azure_virtualnetworks }}"
      loop_control:
      loop_var: vnet
      tags: vnet_facts

工作原理...

在这个示例中,我们概述了一种连接到 Azure 云的替代方法。我们创建了~/.Azure/credentials文件,并将需要连接到 Azure API(tenant_idclient_id等)的相同信息放入其中。由于我们在文件中有这些信息,我们不需要在我们的 Ansible 模块中包含这些参数。

为了验证我们的部署,Ansible 提供了多个事实模块,用于收集 Azure 云中多个对象的操作状态。在这个示例中,我们概述了两个用于收集资源组和虚拟网络事实的模块。我们可以使用Azure_rm_resourcegroup_facts模块收集资源组事实,使用Azure_rm_virtualnetwork_facts收集虚拟网络事实。所有 Azure 事实模块都将这些模块检索的数据注册为 Ansible 事实,这就是为什么我们不需要在自定义定义的变量中注册模块返回的数据。

Azure_rm_resourcegroup_facts模块将输出保存在Azure_resourcegroups Ansible 事实中,我们使用assert模块循环遍历此变量中的所有资源组。然后,我们可以确认它是否使用正确的参数创建。

以下是来自Azure_resourcegroups的片段:

ok: [eu_az_net] => {
 "Azure_resourcegroups": [
 {
 "id": "/subscriptions/bc20fdc0-70fa-46ef-9b80-3db8aa88a25c/resourceGroups/rg_eu_az_net",
 "location": "westeurope",
 "name": "rg_eu_az_net",
 "properties": {
 "provisioningState": "Succeeded"
 }
 }
 ]
}

我们可以使用完全相同的技术来收集使用Azure_rm_virtualnetwork_facts部署的虚拟网络的事实,并使用assert模块验证其状态。

另请参阅...

有关 Azure 中不同网络资源的多个模块的事实收集的更多信息,请使用以下链接:

使用 Ansible 取消 Azure 资源

与我们可以使用自动化大规模创建资源类似,一旦我们决定不再需要这些资源,我们也可以销毁这些资源。使用 Ansible 和 Azure 实施的资源组,这变得更加简化-通过一次正确参数的单个 API 调用,我们可以废弃我们定义的资源组中的所有资源。在本配方中,我们将概述如何执行此操作,以销毁到目前为止我们已经提供的所有资源。

准备工作

Ansible 控制机必须连接到互联网,并能够访问 Azure 公共 API 端点。Azure 帐户还应按照前面的配方进行配置。

如何操作...

  1. 创建一个新的pb_destroy_Azure_net.yml playbook,并添加以下任务以删除所有资源组:
$ cat pb_destroy_Azure_net.yml
---- name: Decomission Azure Infrastructure
 hosts: all
 connection: local
 vars:
 state: absent
 vars_files:
 - Azure_secret.yml
 tasks:
 - name: Delete Resource group
 Azure_rm_resourcegroup:
 tenant: "{{ tenant_id }}"
 client_id: "{{ client_id }}"
 secret: "{{ secret }}"
 location: "{{ region }}"
 subscription_id: "{{ subscription_id }}"
 name: "{{ rg_name }}"
 force_delete_nonempty: yes
 state: "{{ state | default('present') }}"

工作原理...

我们可以使用Azure_rm_resourcegroup Ansible 模块来销毁资源组中的所有资源,以及删除资源组本身。我们可以向模块提供两个重要的参数,以执行delete功能:

  • state设置为absent

  • 包括force_delete_nonempty参数,并将其设置为yes

设置了这些参数后,资源组中的所有资源(虚拟网络、子网等)将被删除,资源组本身也将被删除。

以下输出显示我们的两个资源组不再存在:

以下输出还确认了运行 playbook 后所有虚拟网络都已被删除:

上述截图显示所有虚拟网络已被删除。

第九章:使用 Ansible 部署和操作 GCP 网络资源

Google Cloud 是公共云中的重要参与者之一,它在其Google Cloud PlatformGCP)云上提供了一套全面的服务和功能。在本章中,我们将探讨如何使用 Ansible 自动化在 GCP 云上提供资源,并如何使用各种 Ansible 模块来编排在 GCP 云上构建虚拟网络。

在本章中,我们将使用一个简单的网络设置来说明在 GCP 上使用不同 Ansible 模块构建示例网络的方法。以下图表概述了我们将构建的示例网络:

本章将涵盖以下内容:

  • 安装 GCP SDK

  • 构建 Ansible 清单

  • 验证您的 GCP 账户

  • 创建 GCP VPC 网络

  • 创建子网

  • 在 GCP 中部署防火墙规则

  • 在 GCP 中部署虚拟机

  • 调整 VPC 内的路由

  • 使用 Ansible 验证 GCP 部署

  • 使用 Ansible 取消部署 GCP 资源

技术要求

为了开始使用 GCP,我们需要创建一个账户。您可以在cloud.google.com/free/上设置一个免费的 GCP 账户。

本章中使用的 GitHub 代码可以在github.com/PacktPublishing/Network-Automation-Cookbook/tree/master/ch9_gcp找到。

本章基于以下软件版本:

  • CentOS 7

  • Ansible 2.9

  • Python 3.6.8

查看以下视频以查看代码的实际操作:

bit.ly/3erVlSN

安装 GCP SDK

在本教程中,我们将概述如何安装所需的 Python 库,以便开始使用 Ansible 与 GCP 编排系统进行交互。这一步是强制性的,因为必须在 Ansible 控制机器上安装所需的 Python 库,以便所有 Ansible GCP 模块正常工作。

准备工作

您需要在机器上拥有sudo访问权限才能安装 GCP Python 库。您还需要安装 Python 和 Python pip 包,我们将使用它来安装 GCP 包。

操作步骤...

  1. 按照以下代码安装requests包:
$ sudo pip3 install requests
  1. 按照以下代码安装 Google 认证包:
$ sudo pip3 install google-auth
  1. 创建一个名为ch9_gcp的新文件夹,用于存放本章的所有代码:
$ mkdir ch9_gcp

工作原理...

默认安装的 Ansible 不包括执行 GCP 云模块所需的所有必需的 Python 模块。在本教程中,我们安装了所有 GCP 模块所需的两个 Python 包。第一个包是requests包,主要用于调用 Google 编排系统的 REST API 调用,另一个包是google-auth包,用于对 API 进行身份验证。

另请参阅...

有关如何使用 Ansible 开始与 GCP 进行交互的更多信息,请参阅docs.ansible.com/ansible/latest/scenario_guides/guide_gce.html

构建 Ansible 清单

在本教程中,我们将概述如何构建一个 Ansible 清单,以描述我们将在 GCP 公共云中构建的网络基础架构。这是一个强制性的步骤,我们需要采取这一步骤来定义我们在其中部署基础架构的所有地区的所有 VPC 网络。

操作步骤...

  1. ch9_gcp目录中创建hosts文件,并填入以下数据:
$ cat hosts

[gcp_vpc]
demo_gcp_vpc
  1. 创建ansible.cfg文件,并填入以下内容:
$ cat ansible.cfg

[defaults]
inventory=hosts
retry_files_enabled=False
gathering=explicit
host_key_checking=False
action_warnings=False
  1. 创建group_vars文件夹和gcp_vpc.yml,其中将包含定义我们在此 VPC 中的基础架构的所有变量:
$ mkdir -p group_var/gcp_vpc.yml
  1. 在我们的主文件夹(ch9_gcp)中创建roles目录。该文件夹将包括我们用来创建 GCP 基础架构的所有角色:
$ mkdir -p roles

工作原理...

我们创建了hosts Ansible 清单文件,并声明了我们将在 GCP 云中配置的所有 VPC。在我们的示例设置中,我们有一个单独的 VPC,所以我们创建了一个名为gcp_vpc的单一组,其中包括我们的 VPC(demo_gcp_vpc)。

我们创建了group_vars/gcp_vpc.yml文件,其中将包含我们在此 VPC 中定义基础架构的所有变量。

此时,我们的目录布局如下:

$ tree ch9_gcp
 .
 ├── ansible.cfg
 ├── group_vars
 │ └── gcp_vpc.yml
 ├── hosts
 └── roles

对 GCP 帐户进行身份验证

在本教程中,我们将概述如何创建所需的凭据,以便从 Ansible 对我们的 GCP 帐户进行编程身份验证。这是您需要采取的强制步骤,以便能够在以下教程中运行任何 Ansible 模块。

准备工作

Ansible 控制器必须具有互联网访问权限。此外,执行这些步骤的用户必须具有 GCP 控制台的管理员访问权限,以便创建所需的资源以启用与 GCP API 的编程交互。

如何做...

  1. 使用管理员帐户登录到 GCP 控制台。

  2. 从主控制台中,选择 IAM & admin | Manage Resources。在 GCP 中创建一个新项目,该项目将容纳我们在 GCP 中构建的所有基础设施:

  1. 从主控制台转到 IAM & admin | Service accounts:

  1. 为新的 Ansible 用户创建一个新的服务帐户:

  1. 为这个新的服务帐户分配适当的角色,以便您可以在此 GCP 项目中创建/编辑/删除资源:

  1. 创建并下载将用于对此用户进行身份验证的私钥:

  1. 将下载的 JSON 密钥文件复制到项目目录ch9_gcp并将其重命名为gcp_ansible_secret.json

  2. 在 GCP 控制台上,选择 API & Services 并为当前项目启用 Google Compute Engine API:

它是如何工作的...

为了能够以编程方式访问 GCP API(这是 Ansible 与 GCP 云通信以配置资源的方式),我们需要在我们的 GCP 项目中创建一个称为服务帐户的特殊帐户。此服务帐户类似于用户,但只能访问 GCP 项目的 API。我们创建了此服务帐户并将其称为 Ansible,并为其分配了项目所有者角色,以便在 GCP 项目中具有创建资源的全部权限(在生产环境中,应为此服务帐户分配更严格的角色)。

为了使用此服务帐户对 GCP API 进行身份验证,GCP 为我们提供了一个 JSON 文件,其中包含此帐户的身份信息。此 JSON 文件中包含的主要参数如下:

  • 此服务帐户的私钥

  • 此帐户的类型

  • Project_id

  • Client_id

  • client_email

我们保存这个 JSON 文件并将其复制到我们的目录中,因为我们将在所有的 playbooks 中引用它,以便在 GCP 云上配置资源。最后一步是在我们的 GCP 项目中启用 API;我们需要启用 GCP 计算引擎 API,以便开始与此 API 进行交互,因为默认情况下,在 GCP 项目中 API 访问是被禁用的。

还有更多...

保存所有身份验证信息以对 GCP API 进行身份验证的 JSON 文件是一个关键文件,应该进行安全保护,因此我们将使用 Ansible vault 来保护此文件。

我们创建了一个名为vault_pass的新文件,其中包含我们的 Ansible vault 密码,并更新我们的ansible.cfg文件以指向它,如下面的代码所示:

$ cat ansible.cfg

[defaults]
 vault_password_file=vault_pass

我们使用 Ansible vault 加密 JSON 文件,如下面的代码所示:

$ ansible-vault encrypt gcp-ansible-secret.json

在这个阶段,我们的 JSON 文件是安全的,所有内容都使用vault_pass文件中声明的密码进行加密。

另请参阅...

有关如何在 GCP 中创建新服务账号的更多信息,请访问cloud.google.com/iam/docs/creating-managing-service-accounts

创建 GCP VPC 网络

在 GCP 中,VPC 是用于对所有资源进行分组的主要网络构造。我们可以将它们视为云中的虚拟数据中心。我们需要定义我们的 VPC,以准备好我们的云环境来托管我们的应用程序。在这个示例中,我们将概述如何在 GCP 中定义和配置 VPC。

准备工作

Ansible 控制机必须连接到具有对 GCP 公共 API 端点的可达性的互联网,并且 GCP 帐户应按照前面的示例进行配置。

操作步骤...

  1. 创建一个名为gcp_account_info.yml的新的 YAML 文件,并包含我们的 GCP 登录参数的以下数据:
$ cat gcp_account_info.yml
 ---
 service_account_file: gcp_credentials.json
 project: "gcp-ansible-demo"
 auth_kind: serviceaccount
  1. 创建一个名为gcp_net_build的新 Ansible 角色,如下所示:
$ cd roles

$ ansible-galaxy init gcp_net_build
  1. 更新gcp_net_build/tasks/main.yml文件,添加以下任务以创建我们的 VPC:
- name: Create a New GCP VPC
 gcp_compute_network:
 name: "{{ vpc_name | regex_replace('_','-') }}"
 routing_config:
 routing_mode: "REGIONAL"
 auto_create_subnetworks: no
 state: present
 auth_kind: "{{ auth_kind }}"
 project: "{{ project }}"
 service_account_file: "{{ service_account_file }}"
 register: gcp_vpc
 tags: gcp_vpc
  1. 创建group_vars文件夹,并为 VPC 创建gcp_vpc.yml文件,包括以下数据:
$ cat group_vars/gcp_vpc.yml
 ---
 vpc_name: ansible-demo-vpc
  1. 创建pb_gcp_env_build.yml剧本,包括以下任务以读取保险库加密的 JSON 文件:
---

- name: Build GCP Environment
 hosts: all
 connection: local
 gather_facts: no
 force_handlers: true
 vars_files:
 - gcp_account_info.yml
 tasks:
 - name: Read the Vault Encrypted JSON File
 copy:
 content: "{{ lookup('file','gcp-ansible-secret.json') }}"
 dest: "{{ service_account_file }}"
 notify: Clean tmp Decrypted Files
 tags: always
  1. 使用以下任务更新pb_gcp_env_build.yml剧本以创建所需的 VPC:
 - name: Build GCP Network
 import_role:
 name: gcp_net_build
 tags: gcp_net_build
  1. 更新剧本,包括以下处理程序以删除临时 JSON 凭据文件,如下所示:
 handlers:
 - name: Clean tmp Decrypted Files
 file:
 path: "{{ service_account_file }}"
 state: absent

工作原理...

在这个示例中,我们在之前创建的项目中创建并部署了 GCP VPC。我们使用了 Ansible 角色来构建 GCP 网络的所有组件,第一个任务是使用 Ansible 模块gcp_compute_network创建 VPC。

为了使用任何 Ansible GCP 模块,我们需要对每个模块触发的每个 API 调用进行身份验证,并且我们需要提供以下信息以验证 API 调用:

  • Auth_kind:身份验证类型—在我们的情况下是serviceaccount

  • Project:这是我们创建的当前项目的项目名称。

  • Service_account_file:这是我们创建服务账号时下载的 JSON 文件。

由于我们使用 Ansible vault 来加密保存所有身份验证信息的 JSON 文件的所有内容,因此我们需要在剧本执行期间解密此文件以使用该文件中的数据。此外,由于我们不直接读取此 JSON 文件的内容,而是使用所有 GCP Ansible 模块中的serivce_account_file参数指向它,我们创建了一个任务来使用lookup模块读取此 JSON 文件的内容,并将这些数据存储在临时文件中。通过这种方法,我们可以读取此 JSON 文件中的加密数据,并创建一个新的临时 JSON 文件,其中包含明文数据。我们还可以使用此临时 JSON 文件作为service_account_file的输入。我们使用了一个处理程序任务来在播放结束时删除此临时文件。在播放级别上,我们使用了force_handlers来确保运行处理程序部分内的所有任务,即使我们播放中的任何任务失败。这意味着我们确保保存我们的凭据的明文 JSON 文件始终被删除。

我们将所有前述参数分组并放入gcp_account_info.yml文件中,并将此文件包含在我们的剧本中。我们使用gcp_compute_network模块创建了 VPC,并提供了以下信息以部署 VPC:

  • Name:我们新 VPC 的名称。

  • Auto_create_subnetwork:将其设置为no,因为我们想要创建自定义 VPC 网络,而不是自动模式 VPC 网络。

  • Routing_config:将其设置为Regional,以阻止不同区域子网之间的路由传播。

我们需要强调的一个明显的点是,GCP 中的 VPC 具有全局范围,这意味着它们不绑定到特定区域,而是跨越 GCP 云中的所有区域。另一方面,子网是特定于区域的;然而,由于我们创建了自定义 VPC,在任何区域默认情况下都不会创建子网,我们完全控制在哪里定义我们的子网。在 VPC 范围方面,这种逻辑与 AWS 和 GCP 的 VPC 范围有所不同。

使用gcp_compute_network模块创建 VPC 时,我们必须提供 VPC 名称。在这个任务中,我们使用了regex_replace Ansible 过滤器,以确保 VPC 名称不包含下划线字符(_),因为这不是 VPC 名称中的有效字符。我们使用这个过滤器来替换下划线的任何出现,用破折号(-)来确保 VPC 名称符合 GCP VPC 命名标准。

一旦我们运行我们的 playbook,我们可以看到 VPC 已经创建,在 GCP 控制台上可以看到:

以下片段概述了 Ansible 模块在创建 VPC 后返回的参数:

ok: [demo_gcp_vpc] => {
 "gcp_vpc": {
 "autoCreateSubnetworks": false,
 "changed": true,
 "creationTimestamp": "2019-11-26T12:49:51.130-08:00",
 "failed": false,
 "id": "8661055357091590400",
 "kind": "compute#network",
 "name": "demo-gcp-vpc",
 "routingConfig": {
 "routingMode": "REGIONAL"
 },
 "selfLink": "https://www.googleapis.com/compute/v1/projects/gcp-ansible-demo/global/networks/demo-gcp-vpc"
 }
} 

这些信息很重要,我们将在后续的教程中使用它来创建子网,以便我们可以注册此任务的输出到gcp_vpc变量中,以便在以后的任务中引用它。

还有更多...

默认情况下,当我们在 GCP 中创建一个新项目时,会为该项目创建一个名为default的自动模式 VPC。建议我们删除这个默认网络,因为我们将依赖我们的自定义 VPC 来容纳所有的计算工作负载。

我们可以在我们的项目中看到这个默认 VPC 是存在的,并且它在 GCP 云中的每个区域都有子网,如下面的截图所示:

我创建了一个名为pb_gcp_delete_default_vpc.yml的 playbook 来删除默认 VPC 以及附加到它的所有默认防火墙规则。

另请参阅...

有关 Ansible 中 GCP 虚拟私有云模块以及此模块支持的所有其他参数的更多信息,请访问docs.ansible.com/ansible/latest/modules/gcp_compute_network_module.html#gcp-compute-network-module

创建子网

我们使用子网来分隔我们的 GCP VPC,这是一个工具,允许我们将计算工作负载放入特定区域。此外,子网为我们提供了工具,将我们的虚拟网络分隔成不同的路由和安全域,我们可以控制以提供不同的路由和安全行为在每个子网内。在这个教程中,我们将概述如何在 GCP 云中定义和配置子网。

准备工作

Ansible 控制机必须连接到互联网,可以访问 GCP 公共 API 端点,并且 GCP 帐户应该按照前面的教程进行配置。此外,GCP VPC 需要按照前面的教程进行创建。

如何做...

  1. 使用以下代码更新group_vars/gcp_vpc.yml文件中的子网数据:
$ cat group_vars/gcp_vpc.yml

subnets:
 - name: anz-web
 cidr: 10.1.1.0/24
 region: australia-southeast1

 - name: anz-db
 cidr: 10.1.2.0/24
 region: australia-southeast1

 - name: anz-bastion
 cidr: 10.1.3.0/24
 region: australia-southeast1
  1. 使用以下任务更新gcp_net_build/tasks/main.yml文件来创建我们的子网:
- name: Create Subnets
 gcp_compute_subnetwork:
 name: "{{ subnet.name }}"
 ip_cidr_range: "{{ subnet.cidr }}"
 network: "{{ gcp_vpc}}"
 region: "{{ subnet.region }}"
 state: present
 auth_kind: "{{ auth_kind }}"
 project: "{{ project }}"
 service_account_file: "{{ service_account_file }}"
 loop: "{{ subnets }}"
 loop_control:
 loop_var: subnet
 register: gcp_subnets

工作原理...

在这个教程中,我们创建了我们将在部署中使用的子网。在我们的子网定义中需要注意的第一件事是,我们为每个子网定义了一个区域。这是强制性的,因为正如我们讨论的那样,在 GCP 中,子网具有区域范围,而 VPC 具有全局范围。我们为每个子网定义了一个 CIDR 范围,以及它的名称。

我们使用gcp_compute_subnet模块来创建所有子网。我们使用了之前讨论过的相同参数进行身份验证。为了创建子网,我们指定了以下参数:

  • 名称:我们的子网名称。

  • 区域:此子网将部署的区域。

  • Ip_cidr_range:此子网的 CIDR 块。

  • 网络:我们希望此子网成为其中一部分的 VPC 的引用。我们从创建 VPC 的输出中获取此参数。我们提供gcp_vpc变量,这是来自我们 VPC 创建任务的注册变量。

运行 playbook 后,我们可以看到所有子网都已创建,如下截图所示:

另请参阅...

有关 Ansible 中 GCP 子网模块以及此模块支持的所有其他参数的更多信息,请访问docs.ansible.com/ansible/latest/modules/gcp_compute_subnetwork_module.html

在 GCP 中部署防火墙规则

GCP 提供了许多工具,以强制执行 GCP 云客户环境中的安全性。防火墙规则是 GCP 中支持的最基本的安全工具之一,以实施 VPC 中所有工作负载的第一级防御。在本配方中,我们将概述如何在 GCP 云中定义和配置防火墙规则。

准备工作

Ansible 控制机必须连接到互联网,并能够访问 GCP 公共 API 端点,GCP 帐户应按照前面的配方进行配置。此外,VPC 和子网应按照前面的配方进行配置。

如何做...

  1. 使用以下防火墙规则更新group_vars/gcp_vpc.yml,以保护 Web 和 DB 层之间的流量。
$ cat group_vars/gcp_vpc.yml

fw_rules:
 - name: allow_sql_from_anz-web_to_anz-db
 type: allow
 direction: ingress
 priority: 10
 apply_to: anz-db
 src_tag: anz-web
 dest_tag:
 protocol: tcp
 port: 3389
 state: present
  1. 使用以下防火墙规则更新group_vars/gcp_vpc.yml,以保护流量到 Web 层:
 - name: allow_internet_to-anz-web
 type: allow
 direction: ingress
 priority: 10
 src: 0.0.0.0/0
 apply_to: anz-web
 protocol: tcp
 port: 80,443
 state: present
  1. 使用以下防火墙规则更新group_vars/gcp_vpc.yml,以允许ssh仅限于堡垒主机:
 - name: allow_ssh_to_anz-bastion
 type: allow
 direction: ingress
 priority: 10
 src: 0.0.0.0/0
 apply_to: anz-bastion
 protocol: tcp
 port: 22
 state: present

 - name: allow_ssh_from_bastion_only
 type: allow
 direction: ingress
 priority: 10
 src_tag: anz-bastion
 apply_to: anz-web,anz-db
 protocol: tcp
 port: 22
 state: present
  1. 使用以下任务更新roles/gcp_net_build/tasks.main.yml文件以创建所有必需的防火墙规则:
- name: Create Allow Firewall Rules
 gcp_compute_firewall:
 name: "{{ rule.name | regex_replace('_','-') }}"
 network: {selfLink: "{{ gcp_vpc.selfLink }}"}
 priority: "{{ rule.priority | default(omit) }}"
 direction: "{{ rule.direction | upper | mandatory }}"
 allowed:
 - ip_protocol: "{{ rule.protocol }}"
 ports: "{{ (rule.port|string).split(',') }}"
 source_ranges: "{{ rule.src | default(omit) }}"
 source_tags: "{{ omit if rule.src_tag is not defined else rule.src_tag.split(',') }}"
 destination_ranges: "{{ rule.dest | default(omit) }}"
 target_tags: "{{ omit if rule.apply_to is not defined else rule.apply_to.split(',') }}"
 auth_kind: "{{ auth_kind }}"
 project: "{{ project }}"
 service_account_file: "{{ service_account_file }}"
 loop: "{{ fw_rules | selectattr('type','equalto','allow') | list }}"
 loop_control:
 loop_var: rule
 tags: gcp_fw_rules

工作原理...

GCP 中的防火墙规则是应用于 VPC 中的主机的有状态防火墙规则。GCP 中的防火墙规则可以应用于入站或出站方向,并且有一些默认防火墙规则被定义并应用于 VPC 中的所有主机,如下所示:

  • 在入站方向上,对于任何目标到新自定义 VPC 中的任何主机的所有流量,默认情况下会有一个拒绝所有

  • 在出站方向上,对于新自定义 VPC 中的任何主机发出的所有流量,默认情况下会有一个允许所有

有了上述默认规则,并且由于所有防火墙规则都是有状态的,VPC 中的任何主机发起的任何通信都将被允许;但是,来自 VPC 外部的任何发起的流量将被拒绝。

GCP 防火墙规则可以根据以下标准匹配流量:

  • 源/目标 IPv4 范围

  • IP 协议号

  • TCP/UDP 端口号

  • 网络标记

除了网络标记之外,所有前述标准都是相当明显的。网络标记是特殊的元数据,可应用于 VPC 中的任何主机,以标识和分组这些主机。我们可以使用这些网络标记来作为防火墙规则中的匹配标准,并且仅将防火墙规则应用于 VPC 中的一部分主机。

有了所有这些信息,我们希望在示例网络中的主机上实施以下安全策略:

  • 所有 HTTP/HTTPs 流量应仅允许到我们所有的 Web 服务器。

  • 外部的 SSH 访问应该仅限于我们的堡垒主机。

  • 对我们的 Web 和 DB 服务器的 SSH 访问仅限于堡垒主机。

  • 仅允许来自 Web 到 DB 服务器的 SQL 流量。

我们在一个新的数据结构fw_rules中定义了我们的防火墙规则,这是我们需要应用于我们的 VPC 的所有规则的列表。我们在所有策略中使用网络标记,以便将正确的防火墙规则应用于应强制执行此规则的主机。

我们使用 Ansible 模块gcp_compute_firewall来迭代所有防火墙策略并应用它们。在此模块中,我们可以定义匹配条件,可以基于源/目标 IPv4 地址范围,也可以基于源和目标网络标记。我们定义了我们的任务,以便如果在我们的防火墙规则中未定义参数(例如,源 IPv4 范围),我们应该从提供给模块的参数列表中删除此参数。我们使用omit过滤器来实现此逻辑。

GCP 中的所有防火墙规则都有优先级字段,该字段定义了规则相对于其他规则的优先级以及在处理方面的优先级。没有特定优先级的规则将获得优先级值 1,000。GCP 应用于 VPC 的默认防火墙规则具有优先级值65535,因此我们定义的任何规则都将优先于它们。在所有规则中,我们指定优先级值为10

运行以下任务后,我们可以看到以下规则应用于我们的 VPC,如下截图所示:

另请参阅...

有关 Ansible 中 GCP 防火墙模块以及此模块支持的所有其他参数的更多信息,请访问docs.ansible.com/ansible/latest/modules/gcp_compute_firewall_module.html

在 GCP 中部署 VMs

在本教程中,我们将概述如何在 GCP 中的 VPC 中使用我们部署的正确子网部署虚拟机(使用 Google Compute Engine)。我们还将分配正确的网络标记,以强制执行这些机器上的正确安全策略。

准备工作

Ansible 控制机必须连接到具有对 GCP 公共 API 端点的可达性的互联网,并且 VPC、子网和防火墙规则需要按照前几章中概述的方式部署。

操作步骤...

  1. 更新group_vars/gcp_vpc.yml文件,包括描述我们将在所有 VM 上使用的 flavor 和 OS 的所需信息:
$ cat group_vars/gcp_vpc.yml

compute_node_flavor: f1-micro
compute_node_images: projects/centos-cloud/global/images/family/centos-7
compute_node_image_size: 10
  1. 更新group_vars/gcp_vpc.yml文件,包括描述我们计算节点所需信息:
$ cat group_vars/gcp_vpc.yml
compute_nodes:
 - name: web-server-1
 network: anz-web
 has_internet: yes
 zone: australia-southeast1-a

< -- Output Omitted for Brevity -- >

 - name: db-server-1
 network: anz-db
 has_internet: no
 zone: australia-southeast1-a

< -- Output Omitted for Brevity -- >

 - name: bastion-host
 network: anz-bastion
 ip: 10.1.3.253
 has_internet: yes
 ip_forwarding: yes
 zone: australia-southeast1-a
  1. 创建一个新的 Ansible 角色(gcp_vm_build)来在 GCP 上部署 VM 工作负载:
$ cd roles

$ ansible-galaxy init gcp_vm_build
  1. 使用以下任务更新gcp_vm_build/tasks/main.yml文件以创建 VM 的磁盘:
- name: create a disk for {{ node.name }}
 gcp_compute_disk:
 name: "{{ node.name | regex_replace('_','-') }}-disk"
 size_gb: "{{compute_node_image_size }}"
 source_image: "{{ compute_node_images }}"
 zone: "{{ node.zone }}"
 auth_kind: "{{ auth_kind }}"
 project: "{{ project }}"
 service_account_file: "{{ service_account_file }}"
 state: present
 register: gcp_vm_disk

  1. 使用以下任务更新gcp_vm_build/tasks/main.yml文件以创建没有公共 IP 地址的 VM:
- name: create a {{ node.name }} instance with no Internet
 gcp_compute_instance:
 name: "{{ node.name | regex_replace('_','-') }}"
 machine_type: "{{ compute_node_flavor }}"
 disks:
 - source: "{{ gcp_vm_disk }}"
 boot: 'true'
 network_interfaces:
 - network: "{{ gcp_vpc }}"
 subnetwork: "{{ gcp_subnets.results |
 selectattr('name','equalto',node.network) |
 list | first }}"
 metadata:
 tier: "{{ node.name.split('-')[0] }}"
 tags:
 items: "{{ node.network }}"
 zone: "{{ node.zone }}"
 auth_kind: "{{ auth_kind }}"
 project: "{{ project }}"
 service_account_file: "{{ service_account_file }}"
 state: present
 when: not node.has_internet
  1. 使用以下任务更新gcp_vm_build/tasks/main.yml文件以创建具有公共 IP 地址的 VM:
- name: create an {{ node.name }} instance with Internet
 gcp_compute_instance:
 name: "{{ node.name | regex_replace('_','-') }}"
 machine_type: f1-micro
 can_ip_forward: "{{ node.ip_forwarding if node.ip_forwarding is defined else 'no' }}"
 disks:
 - source: "{{ gcp_vm_disk }}"
 boot: 'true'
 network_interfaces:
 - network: "{{ gcp_vpc }}"
 network_ip: "{{ node.ip if node.ip is defined else omit }}"
 subnetwork: "{{ gcp_subnets.results |
 selectattr('name','equalto',node.network) |
 list | first }}"
 access_configs:
 - name: External NAT
 type: ONE_TO_ONE_NAT
 metadata:
 tier: "{{ node.name.split('-')[0] }}"
 zone: "{{ node.zone }}"
 tags:
 items: "{{ node.network }}"
 auth_kind: "{{ auth_kind }}"
 project: "{{ project }}"
 service_account_file: "{{ service_account_file }}"
 state: present
 register: vm_data
 when: node.has_internet
  1. 使用以下任务更新pb_gcp_env_build.yml剧本以创建我们定义的所有所需 VM:
 - name: Build VM Instances
 include_role:
 name: gcp_vm_build
 loop: "{{ compute_nodes }}"
 loop_control:
 loop_var: node

工作原理...

根据我们示例网络的设计,我们将在两个不同的可用性区域中部署两个 Web 服务器和两个数据库服务器。然后,我们将在单个 AZ 中构建一个堡垒主机,因为它仅用于管理。我们在compute_nodes变量中定义了所有所需的机器,并且对于每台机器,我们指定了以下我们在规划期间将使用的参数:

  • 名称:机器的名称

  • 网络:指定我们将部署此计算机的子网,并强制执行正确的网络标记

  • 区域:指定我们要部署此计算机的区域

  • has_internet:表示此计算机是否应获取公共 IP 地址

我们创建了一个新的角色来部署我们的计算工作负载,并定义了以下主要部分:

  • 为 VM 创建磁盘:初始任务是创建将容纳这些机器的操作系统的磁盘。我们使用gcp_compute_disk Ansible 模块来定义这些磁盘,并指定了以下参数:

  • 名称:这是此磁盘的名称。

  • Image_source:指定机器将运行的操作系统-在我们的示例中,所有机器都将运行 CentOS。

  • 区域:指定将创建此磁盘的可用性区域。

  • Size_gb:指定将要部署的磁盘大小。

  • 创建 VMs:在创建磁盘之后,我们使用gcp_compute_instance模块创建了 VMs,该模块使用以下参数来部署 VM:

  • Name:这个 VM 的名称。

  • Machine_type:指定我们用于这些机器的实例类型。

  • Disks:一个字典,指定我们将与这台机器一起使用的磁盘。我们提供gcp_vm_disk变量,这是我们在前一个任务中部署磁盘时获得的。

  • Network_interfaces:一个字典,指定我们需要在哪个子网和 VPC 上部署这个实例。对于 VPC,我们提供gcp_vpc变量,这是我们在部署 VPC 时获得的值。

  • Zone:指定我们将在哪个可用区部署我们的 VM。

  • 标签:指定我们将分配给这些 VM 的网络标签。这些标签与我们在防火墙规则中使用的标签相同,以便引用我们的计算节点。

除了前面的参数,我们还有access_configs参数(它是一个字典),用于指定计算节点是否会获得公共 IP 地址。如果 VM 获得公共 IP 地址,我们将access_configs中的 name 参数设置为 external NAT,type 参数设置为ONE_TO_ONE_NAT。如果机器不需要公共 IP 地址,我们将省略access_configs字典。

在我们的设置中,所有的 Web 服务器和堡垒主机都应该获得一个公共 IP 地址;然而,我们的数据库服务器不应该直接连接互联网,因此不应该为它们分配公共 IP 地址。我们使用has_internet参数来区分这一点,在计算节点定义中使用这个参数来选择在 VM 配置期间使用的正确任务。

一旦我们使用新角色运行剧本来创建 VMs,我们将看到每个 VM 的所有磁盘都已创建,如下截图所示:

此外,所有的 VM 都是在正确的子网中创建的,如下截图所示:

一旦我们的 VM 使用了正确的网络标签创建,我们可以验证我们的防火墙规则只应用于基于这些网络标签的 VM。以下代码段概述了防火墙规则allow-internet-to-anz-web以及它是如何仅应用于 Web 服务器的:

另请参阅...

有关 Ansible 中 GCP 实例和磁盘模块以及这些模块支持的所有其他参数的更多信息,请访问以下链接:

调整 VPC 内的路由

在这个配方中,我们将概述如何控制 GCP VPC 内的路由,以强制执行主机的自定义路由决策。这使我们能够完全控制 VPC 内主机的路由。

准备工作

Ansible 控制机必须连接到互联网,能够访问 GCP 公共 API 端点,并且 GCP 帐户应该按照前面的配方进行配置。此外,资源组、虚拟网络和子网应该按照前面的配方进行部署。

操作步骤...

  1. 更新group_vars/gcp_vpc.yml文件,包括所需的路由数据,如下所示:
$ cat group_vars/gcp_vpc.yml
route_tables:
 - name: db_tier_rt
 subnet: db_tier
 routes:
 - name: Default Route
 prefix: 0.0.0.0/0
 nh: none
  1. 更新pb_gcp_env_build.yml剧本,添加以下任务以在 GCP 中创建路由:
- name: Create the Route
 gcp_compute_route:
 name: "{{ route.name }}"
 dest_range: "{{ route.dest}}"
 network: {selfLink: "{{ gcp_vpc.selfLink }}"}
 next_hop_ip: "{{ route.nh }}"
 tags: "{{ route.apply_to.split(',') | default(omit) }}"
 state: present
 auth_kind: "{{ auth_kind }}"
 project: "{{ project }}"
 service_account_file: "{{ service_account_file }}"
 loop: "{{ cutom_routes }}"
 loop_control:
 loop_var: route
 tags: gcp_route

工作原理..

在我们的示例设置中,根据当前的路由和防火墙规则,我们的数据库服务器无法连接到互联网;但是,我们需要能够从这些服务器访问互联网,以安装软件或执行补丁。为了实现这个目标,我们将使用我们的堡垒主机作为 NAT 实例,为我们的数据库服务器提供互联网访问。为了实现这一目标,我们需要调整 VPC 中所有数据库服务器的路由。

在 GCP 中,我们有一个默认路由,指向 VPC 中的互联网网关。这个默认路由存在于 VPC 中,并应用于 VPC 内的所有主机。以下是我们 VPC 的路由表:

然而,由于现有的防火墙规则以及所有数据库服务器都没有外部公共 IP 地址,数据库服务器将无法访问互联网。我们需要调整数据库服务器的路由,指向执行 NAT 的堡垒主机。我们还需要保留原始的默认路由,因为这是我们的 Web 和堡垒主机用来访问互联网的主要路径。

我们使用custom_routes列表数据结构定义需要应用的自定义路由,并使用gcp_compute_route Ansible 模块循环遍历这个数据结构,创建所有需要的路由。我们使用在 DB 主机上应用的网络标记,以强制仅在具有此网络标记的主机上应用此路由。运行这个新任务后,VPC 的更新路由表如下截图所示:

我们可以将路由的下一跳设置为 IP 地址或实例标识;但是,为了简单起见,我们使用了 IP 地址,并在 VM 定义中选择了堡垒主机的静态 IP 地址,以便在我们的路由设置中轻松引用这个 IP 地址。

我们在主要 playbook 中创建了这个路由任务,因为我们需要有堡垒 VM 才能设置下一跳 IP 地址的路由。如果在 VM 被部署之前创建路由,路由将被创建;但是,任务将失败,并显示警告,指出我们的路由的下一跳 IP 地址不存在。

另请参阅...

有关 Ansible 中 GCP 路由模块和此模块支持的所有其他参数的更多信息,请转到docs.ansible.com/ansible/latest/modules/gcp_compute_route_module.html#gcp-compute-route-module

使用 Ansible 验证 GCP 部署

Ansible 提供了多个模块来收集我们在 GCP 中创建的不同资源的操作状态,我们可以使用这些模块来验证 GCP 云中我们网络资源的当前状态。这提供了一种编程方法来验证部署,而无需登录到图形用户界面GUI)通过门户网站检查 GCP 中部署的不同组件的状态。在本示例中,我们将概述如何使用一些 Ansible 模块来验证我们部署的网络子网。

准备工作

Ansible 控制机必须连接到互联网,并能够到达 GCP 公共 API 端点,GCP 帐户应按照前面的示例进行配置。

如何做...

  1. 创建一个新的pb_gcp_net_validate.yml playbook,并添加以下任务以收集 VPC 子网信息:
$ cat pb_gcp_net_validate.yml

---

- name: Build GCP Environment
 hosts: all
 connection: local
 gather_facts: no
 force_handlers: True
 vars_files:
 - gcp_account_info.yml
 tasks:
 - name: Get Subnet Facts
 gcp_compute_subnetwork_facts:
 region: "{{ subnets | map(attribute='region') | list | first }}"
 auth_kind: "{{ auth_kind }}"
 project: "{{ project }}"
 service_account_file: "{{ service_account_file }}"
 register: gcp_vpc_subnets
  1. 使用以下任务更新 playbook 以验证部署的所有子网上的 IP 前缀:
 - name: Validate all Subnets are Deployed
 assert:
 that:
 - gcp_vpc_subnets['items'] | selectattr('name','equalto',item.name) |
 map(attribute='ipCidrRange') | list | first
 == item.cidr
 loop: "{{ subnets }}"

它是如何工作的...

我们创建了一个新的 playbook,用于验证我们在 GCP 项目中部署的所有子网。Ansible 提供多个模块来收集 GCP 中不同资源(子网、VPC、虚拟机等)的操作状态或事实。在本例中,我们使用gcp_compute_subnetwork_facts模块来收集我们部署的子网事实。我们将此模块返回的所有数据注册到一个新变量gcp_vpc_subnets中。最后,我们使用assert模块循环遍历所有子网定义,验证所有这些子网上分配的 IP 前缀是否正确并与我们的设计一致。

我们可以使用其他收集事实的模块来验证部署的其他方面,并使用多个assert语句来确保所有部署的资源与我们的设计一致。

另请参阅...

有关其他 GCP 收集事实的模块的更多信息,请访问以下链接:

使用 Ansible 销毁 GCP 资源

与使用自动化规模创建资源类似,一旦决定不再需要这些资源,我们可以销毁这些资源。我们使用与在 GCP 中创建资源相同的 Ansible 模块来销毁这些资源。

准备工作

Ansible 控制机必须连接到互联网,并能够访问 GCP 公共 API 端点,GCP 帐户应按照前面的示例进行配置。

如何操作...

  1. 创建一个新的pb_gcp_env_destroy.yml playbook,并添加以下任务以读取保险柜加密的 JSON 文件:
$ cat pb_gcp_env_destroy.yml

---

- name: Decommission GCP Resources
 hosts: all
 connection: local
 force_handlers: True
 environment:
 GCP_SERVICE_ACCOUNT_FILE: "{{ service_account_file }}"
 GCP_AUTH_KIND: 'serviceaccount'
 vars_files:
 - gcp_account_info.yml
 tasks:
 - name: Read the Vault Encrypted JSON File
 copy:
 content: "{{ lookup('file','gcp-ansible-secret.json') }}"
 dest: "{{ service_account_file }}"
 notify: Clean tmp Decrypted Files
 tags: always
  1. 更新pb_gcp_env_destroy.yml playbook,并添加以下任务以收集 VPC 信息:
 - name: Get VPC Facts
 gcp_compute_network_facts:
 project: "{{ project }}"
 register: gcp_vpc
  1. 使用以下任务更新 playbook 以删除所有 VM:
 - name: Delete Instance {{ node.name }}
 gcp_compute_instance:
 name: "{{ node.name | regex_replace('_','-') }}"
 zone: "{{ node.zone }}"
 project: "{{ project }}"
 state: absent
 loop: "{{ compute_nodes }}"
 loop_control:
 loop_var: node
  1. 使用以下任务更新 playbook 以删除我们在 VPC 中为所有 VM 创建的所有磁盘:
 - name: Delete disks for {{ node.name }}
 gcp_compute_disk:
 name: "{{ node.name | regex_replace('_','-') }}-disk"
 zone: "{{ node.zone }}"
 project: "{{ project }}"
 state: absent
 loop: "{{ compute_nodes }}"
 loop_control:
 loop_var: node
  1. 使用以下任务更新 playbook 以删除 VPC 中的所有防火墙规则:
 - name: Delete All Firewall Rules
 gcp_compute_firewall:
 name: "{{ rule.name | regex_replace('_','-') }}"
 network: "{{ gcp_vpc }}"
 project: "{{ project }}"
 state: absent
 loop: "{{ fw_rules }}"
 loop_control:
 loop_var: rule
 tags: gcp_fw_rules
  1. 使用以下任务更新 playbook 以删除 VPC 中的所有自定义路由:
- name: Delete all Routes
 gcp_compute_route:
 name: "{{ route.name }}"
 dest_range: "{{ route.dest}}"
 network: "{{ gcp_vpc }}"
 project: "{{ project }}"
 state: absent
 loop: "{{ custom_routes }}"
 loop_control:
 loop_var: route
 when:
 - custom_routes is defined
  1. 使用以下任务更新 playbook 以删除 VPC 中的所有子网:
 - name: Delete GCP Subnets
 gcp_compute_subnetwork:
 name: "{{ subnet.name }}"
 ip_cidr_range: "{{ subnet.cidr }}"
 network: "{{ gcp_vpc }}"
 region: "{{ subnet.region }}"
 project: "{{ project }}"
 state: absent
 loop: "{{ subnets }}"
 loop_control:
 loop_var: subnet
  1. 使用以下任务更新 playbook 以删除所有 VPC:
 - name: Delete GCP VPC
 gcp_compute_network:
 name: "{{ vpc_name | regex_replace('_','-') }}"
 project: "{{ project }}"
 state: absent

工作原理...

我们创建了一个新的 playbook,用于销毁我们样本网络设计中的所有资源。我们使用了与在 GCP 云中部署资源相同的模块;但是,我们使用了state: absent来删除所有这些资源。

在销毁资源时唯一需要注意的是删除这些资源的顺序。如果仍有依赖于要删除的资源的活动依赖资源,我们就不能删除任何资源。例如,我们不能在没有先删除使用此磁盘的 VM 的情况下删除磁盘。

运行 playbook 后,我们可以看到所有 VM 都已删除,如下面的截图所示:

此外,所有我们的 VPC 和子网也已删除,如下面的截图所示:

上述截图显示当前项目中没有本地 VPC 网络。

第十章:使用 Batfish 和 Ansible 进行网络验证

在本书的所有先前章节中,我们使用了多个示例来概述如何使用 Ansible 执行网络验证——我们通过利用 Ansible 中可用的不同模块来实现这一点。在所有这些情况下,我们在将配置推送到网络设备后执行了网络验证。然后,我们收集了网络状态并验证其是否与我们预期的状态一致。然而,我们可能希望在将配置推送到设备之前验证网络状态。此外,可能需要验证预期的网络状态是否符合要求,甚至在不触及网络的情况下进行验证。但是我们该如何做到呢?

Batfish 是一个针对这种用例的开源项目。它的主要目标是提供一个离线网络验证工具,以验证网络配置的多个方面。Batfish 可以为网络提供安全性、合规性和流量转发的验证和正确性保证。它使用我们网络设备的设备配置来构建我们网络的中立数据模型和转发树,然后我们可以使用它来验证网络状态和验证网络内的正确流量转发。以下图表概述了 Batfish 的高级架构以及其工作原理:

Batfish 使用客户端/服务器模型。通过这种模型,我们运行一个 Batfish 服务器实例(用 Java 编写),并使用一个名为 Pybatfish 的客户端软件开发工具包(用 Python 编写)与服务器进行通信。然后,我们使用网络配置文件初始化我们网络的快照,并且基于这个网络快照,Batfish 服务器为我们的网络计算了一个数据模型。使用客户端,我们可以开始使用这个供应商中立的数据模型来验证我们的网络。

Batfish 团队开发了多个 Ansible 模块,这些模块包装了 Pybatfish 客户端库,以检索 Batfish 服务器生成的数据模型。这些模块允许我们针对网络模型执行不同的查询,以验证我们的网络状态。接下来的图表概述了 Ansible、Pybatfish 和 Batfish 服务器之间的交互。

在本章中,我们将概述如何安装 Batfish 以及如何将其与 Ansible 集成,以便开始使用它来验证网络状态,而不是将配置推送到我们的设备。这种组合非常强大,并且可以轻松扩展到构建用于网络配置更改的完整持续集成/持续部署(CI/CD)流水线。Batfish 可以成为在将配置推送到生产网络设备之前提供预验证的重要组成部分。

我们将使用以下网络拓扑,该拓扑在*第四章中使用,构建数据中心网络与 Arista 和 Ansible,*来概述如何使用 Ansible 和 Batfish 验证样本叶脊网络拓扑:

本章涵盖的主要内容有:

  • 安装 Batfish

  • 将 Batfish 与 Ansible 集成

  • 生成网络配置

  • 为 Batfish 创建网络快照

  • 使用 Ansible 初始化网络快照

  • 从 Batfish 收集网络信息

  • 使用 Batfish 验证流量转发

  • 使用 Batfish 验证访问控制列表(ACLs)

技术要求

本章中描述的所有代码都可以在以下网址找到:github.com/PacktPublishing/Network-Automation-Cookbook/tree/master/ch10_batfish

本章基于以下软件版本:

  • 运行 CentOS 7.7 的 Ansible 机器

  • 托管 Batfish 容器的 CentOS 7.7 机器

  • Python 3.6.8

  • Ansible 2.9

  • Arista 虚拟化可扩展操作系统vEOS)运行 EOS 4.20.1F

查看以下视频以查看代码的实际操作:

bit.ly/3bhke1A

安装 Batfish

在这个步骤中,我们将概述如何安装 Batfish 容器(Batfish 架构中的服务器组件)并启动它,以便从 Ansible 开始与其进行交互。这是一个必要的基础步骤,以便开始使用 Batfish 验证我们的网络。

准备工作

如本章介绍所述,我们将在单独的 Linux 机器上安装 Batfish。这台机器需要具有互联网连接,以便能够安装 Docker 并下载 Batfish 容器。

如何做…

  1. 在 CentOS Linux 机器上安装 Docker,如下面的网址所示:

docs.docker.com/install/linux/docker-ce/centos/

  1. 一旦 Docker 安装并运行,下载 Docker 容器,如下面的代码片段所示:
$ sudo docker pull batfish/batfish
  1. 启动 Batfish 容器,如下面的代码片段所示:
$ sudo docker run -d -p 9997:9997 -p 9996:9996 batfish/batfish

它是如何工作的…

Batfish 提供了多种安装和运行 Batfish 服务器的选项。然而,最简单和最推荐的方法是运行一个包含 Batfish 服务器的 Docker 容器。为了运行这个 Docker 容器,我们首先需要在 CentOS Linux 机器上安装 Docker。在我们的情况下,Docker 可以安装在不同的 Linux 发行版上,也可以安装在 macOS 和 Windows 上。

一旦安装了 Docker,我们使用 docker pull 命令将 Batfish 容器下载到我们的 Linux 机器上,并使用 docker run 命令启动 Docker 容器。我们必须从容器中暴露 传输控制协议TCP)端口 99969997,并将它们映射到 Linux 机器上,使用 -p 指令。我们将这些端口映射到 Linux 机器上的相同端口。这些端口用于从远程客户端(安装在 Ansible 控制机器上的 Pybatfish 客户端库)与 Batfish 服务器进行交互。

Batfish 提供两个 Docker 容器:batfish/batfishbatfish/allinonebatfish/allinone 容器包含 Batfish 服务器和 Pybatfish 客户端库。它还安装了 Jupyter Notebook Python 库以及一些示例笔记本,以开始与 Batfish 服务器进行交互。但是,我们不会使用这种方法。相反,我们将使用 batfish/batfish 容器,它只包含 Batfish 服务器。

另请参阅…

有关 Batfish 及其安装方法的更多信息,请访问以下网址:

将 Batfish 与 Ansible 集成

为了将 Batfish 与 Ansible 集成,我们需要安装所需的 Python 包。这样做将允许 Ansible 与 Batfish 服务器进行通信。在这个步骤中,我们将概述如何安装这些 Python 包,以及如何安装运行所需的 Batfish Ansible 模块所需的 Ansible 角色。

准备工作

为了按照这个步骤进行操作,Ansible 控制器必须具有互联网连接。这将允许我们安装 Batfish 所需的依赖项。

如何做…

  1. 在 Ansible 控制器上安装 Batfish 客户端 python3 包,如下面的代码片段所示:
$ sudo python3 -m pip install --upgrade git+https://github.com/batfish/pybatfish.git
  1. 下载 batfish Ansible 角色到 roles 文件夹中,如下面的代码片段所示:
$ ansible-galaxy install batfish.base

它是如何工作的…

在这个步骤中,我们正在设置 Ansible 和 Batfish 之间的集成。这通过两个步骤完成:

  1. 在 Ansible 控制器上,我们需要安装pybatfish Python 库,这是与 Batfish 服务器交互的 Batfish 客户端 SDK。这个包是 Ansible 模块所需的。这些将被用于在我们的 playbooks 中与 Batfish 服务器交互。

  2. 其次,我们安装了 Batfish 团队开发的batfish角色,以便与 Batfish 服务器交互并验证网络设备配置。这个 Ansible 角色包含了运行 Batfish 自定义 Ansible 模块所需的所有 Python 脚本。为了将此角色安装到 Ansible 控制机上,我们使用ansible-galaxy

我们可以验证pybatfish是否正确安装,如下所示:

$ pip3 freeze | grep batfish
pybatfish==0.36.0

现在我们可以探索由ansible-galaxy下载的已安装角色:

$ ansible-galaxy list batfish.base

# /home/ansible/.ansible/roles

以下是此角色的 Python 源代码列表,该列表位于此角色的library文件夹中:

$tree ~/.ansible/roles/batfish.base/library/
/home/ansible/.ansible/roles/batfish.base/library/
├── bf_assert.py
├── bf_extract_facts.py
├── bf_init_snapshot.py
├── bf_session.py
├── bf_set_snapshot.py
├── bf_upload_diagnostics.py
└── bf_validate_facts.py

通过完成这两个步骤,Ansible 控制器已经准备好开始与我们在上一个示例中部署的 Batfish 服务器进行交互。

由于我们没有在ansible-galaxy install命令上指定任何额外的参数,这些角色将默认安装在~/.ansible/roles路径上。

另请参阅...

有关 Pybatfish 和 Batfish 开发的 Ansible 角色以及与 Ansible 一起使用的更多信息,请查看此页面:github.com/batfish/batfish/blob/master/README.md

生成网络配置

要开始使用 Batfish 进行分析和验证,我们需要向 Batfish 服务器提供我们网络设备的配置。在这个示例中,我们将概述如何使用 Ansible 生成这个配置。Batfish 是一种离线网络验证工具,拥有完整的网络配置是实现正确的网络验证的必要步骤之一。

准备工作

这里没有特定的要求,除了在 Ansible 控制机上安装了 Ansible。

如何做...

  1. 创建一个名为ch10_batfish的新文件夹,其中将包含所有我们的变量和 playbooks。

  2. 填充所有变量以描述我们的网络在group_vars/all.yml文件和host_vars文件夹中。在这里,我们使用的是第四章中概述的完全相同的变量,使用 Arista 和 Ansible 构建数据中心网络

  3. ch10_batfish文件夹内创建一个roles文件夹,以容纳我们将创建的所有角色。

  4. 创建一个名为generate_fabric_config的新角色,如下所示:

$ cd ch10_batfish

$ ansible-galaxy init --init-path roles generate_fabric_config
  1. 构建templates文件夹中的所有 Jinja2 模板,以创建接口、管理和边界网关协议BGP)配置。

  2. tasks/main.yml文件中包含构建配置所需的所有任务。同样,我们使用的是第四章中已经讨论过的完全相同的步骤和模块,使用 Arista 和 Ansible 构建数据中心网络,以构建此示例网络的配置。

  3. 创建ansible_host清单,如下面的代码块所示:

$ cat hosts [leaf] leaf01   ansible_host=172.20.1.41 leaf02    ansible_host=172.20.1.42 leaf03    ansible_host=172.20.1.43 leaf04    ansible_host=172.20.1.44 [spine] spine01     ansible_host=172.20.1.35 spine02     ansible_host=172.20.1.36
[arista:children] leaf spine
  1. 现在,创建一个名为pb_build_fabric_config.yml的新 playbook,如下所示:
$ cat pb_build_fabric_config.yml
---

- name: Build DC Fabric Config
 hosts: all
 connection: local
 gather_facts: no
 vars:
 tmp_dir: tmp
 config_dir: configs
 roles:
 - generate_fabric_config

工作原理...

在这个示例中,我们使用 Ansible 来生成我们示例拓扑中网络设备的配置。我们还使用了我们在第四章中讨论过的完全相同的数据和变量结构,使用 Arista 和 Ansible 构建数据中心网络。我们使用 YAML 文件将所有基础设施定义分组在group_varshost_vars文件夹中。我们还使用了在第四章中使用的完全相同的 Jinja2 模板,使用 Arista 和 Ansible 构建数据中心网络,以生成接口、BGP 和设备管理配置的配置片段。

我们使用ansible-galaxy init命令构建角色骨架,并使用--init-path指令指定在哪里创建这个新角色。

以下输出概述了我们用于生成设备配置的新角色的结构:

$ tree roles/generate_fabric_config
roles/generate_fabric_config
├── meta
│   └── main.yml
├── tasks
│   └── main.yml
└── templates
 ├── intf.j2
 ├── mgmt.j2
 ├── overlay_bgp.j2
 └── underlay_bgp.j2

在这一点上,我们创建一个新的 playbook 来生成设备配置,并使用connection本地参数,因为我们需要在 Ansible 控制器节点上捕获网络设备的配置。运行完 playbook 后,我们将得到所有设备的配置在configs文件夹中,如下面的代码块所示:

$ tree ch10_batfish/configs
configs
├── leaf01.cfg
├── leaf02.cfg
├── leaf03.cfg
├── leaf04.cfg
├── spine01.cfg
└── spine02.cfg

为 Batfish 创建网络快照

为了让 Batfish 能够使用设备的配置文件来分析网络,这些文件需要按特定顺序结构。这样 Batfish 服务器就可以轻松地摄取这些数据。

在这个教程中,我们将概述如何正确地结构和准备我们的网络配置文件,以便 Batfish 服务消费。

准备工作

设备配置应该已经生成,就像在上一个教程中演示的那样。

操作步骤...

  1. 创建一个名为pb_batfish_analyis.yml的新 playbook,并添加以下任务以创建一个新文件夹。这个文件夹将存放batfish分析所需的网络配置:
$ cat pb_batfish_analyis.yml
---

- name: Extract network device facts using Batfish and Ansible
 hosts: all
 gather_facts: no
 roles:
 - batfish.base
 vars:
 ansible_connection: local
 batfish_host: 172.20.100.101
 config_dir: configs
 batfish_network_folder: batfish_net_snapshot
 batfish_analysis_folder: batfish_analysis
 tasks:

 - name: Create a Batfish Config Directory
 file:
 path: "{{ batfish_network_folder }}"
 state: directory    run_once: yes
  1. 更新名为pb_batfish_analyis.yml的 playbook,添加以下任务以将所有配置文件复制到新文件夹中:
- name: copy All configs to Batfish Directory
 copy:
 src: "{{ config_dir }}"
 dest: "{{ batfish_network_folder }}"
 run_once: yes

工作原理…

为了开始对我们的网络进行分析,我们创建一个新的 playbook,用于执行所有必需的任务,并使用 Batfish 验证网络配置。在这个 playbook 中,我们使用以下参数:

  • 我们在网络中的所有节点上运行 playbook。这是因为我们需要在随后的任务中引用每个节点的参数(如环回互联网协议IP))。

  • 我们将ansible_connection参数设置为local,因为我们不需要连接到设备,所有任务将在 Ansible 机器上本地运行。

  • 我们指定 Batfish 服务器机器的 IP 地址,该服务器托管batfish容器。这将在随后的所有任务中用于与 Batfish 服务器通信。

为了让 Batfish 开始分析设备的配置,设备的配置文件需要按特定顺序结构在一个目录中。这一步通常被称为为 Batfish 分析准备网络快照。

在这里,我们为 Batfish 分析创建一个新的 playbook。在第一个任务中,我们创建configs文件夹,这将是 Batfish 用来检索网络设备配置的基础。

在第二个任务中,我们使用copy模块将网络设备的配置文件复制到configs文件夹中。一旦我们运行了包含指定任务的 playbook,我们将得到 Batfish 分析所需的以下目录结构:

 $ tree ch10_batfish/batfish_net_snapshot/

 batfish_net_snapshot
└── configs
 ├── leaf01.cfg
 ├── leaf02.cfg
 ├── leaf03.cfg
 ├── leaf04.cfg
 ├── spine01.cfg
 └── spine02.cfg

在所有任务中,我们都使用run_once参数,因为我们只想创建文件夹并复制文件一次。如果我们省略此选项,将会对清单中的每个节点运行这些任务,这在这种情况下并不理想。

另请参阅...

有关 Batfish 网络快照所需的目录结构的更多信息,请访问此链接:pybatfish.readthedocs.io/en/latest/notebooks/interacting.html#Uploading-configurations

使用 Ansible 初始化网络快照

在这个教程中,我们将概述如何在 Ansible 和 Batfish 服务器之间建立会话。除此之外,我们还将看看如何初始化我们在上一步准备的网络快照,并将其发送到 Batfish 服务器。

准备工作

如前一篇文章所述,设备配置是在此时生成的,并且网络快照已经打包好了。此外,现在还在 TCP 端口 9996 和 9997 上提供了 Ansible 控制器与 Batfish 服务器之间的 IP 可达性。

如何做...

  1. 使用以下任务更新pb_batfish_analyis.yml剧本,以与 Batfish 服务器开始会话:
 - name: Setup connection to Batfish service
 bf_session:
 host: "{{ batfish_host }}"
 name: local_batfish
 register: bf_session
 run_once: yes
  1. 更新pb_batfish_analyis.yml剧本以在 Batfish 服务器上初始化网络快照:
 - name: Initialize the Network Snapshot
 bf_init_snapshot:
 network: arista_dc_fabric
 snapshot: arista_dc_fabric_config
 snapshot_data: "{{ batfish_network_folder }}"
 overwrite: true
 run_once: yes
 register: bf_snapshot

工作原理...

在剧本中,我们使用了从ansible-galaxy下载的batfish.base Ansible 角色与 Batfish 服务器进行交互。该角色提供了多个模块,我们使用这些模块来启动 Ansible 控制机和 Batfish 服务器之间的集成。

第一个模块是bf_session。该模块在 Batfish 客户端(在本例中为 Ansible)和 Batfish 服务器之间打开会话,以便开始在两者之间交换数据。第二个模块br_init_snapshot初始化了我们在 Ansible 控制器上创建的网络快照(设备配置文件)。然后将它们发送到 Batfish 服务器,以便在 Batfish 服务器上开始分析,并且 Batfish 服务器根据这些配置文件为我们的网络构建中立的数据模型。

bf_init_session模块返回了 Batfish 解析配置的状态,以及在解码配置时是否出现任何问题。我们将这个返回值捕获在bf_snapshot变量中。以下代码段概述了 Batfish 在提供的网络快照上执行的解析状态:

ok: [localhost] => {
 "bf_snapshot": {
 "ansible_facts": {

 "bf_network": "arista_dc_fabric",
 "bf_snapshot": "arista_dc_fabric_config"
 },
 "result": {
 "network": "arista_dc_fabric",
 "snapshot": "arista_dc_fabric_config"
 },
 "summary": "Snapshot 'arista_dc_fabric_config' created in network
'arista_dc_fabric'",
 "warnings": [ 
 "Your snapshot was successfully initialized but Batfish failed to fully recognize some lines in one or more input files. Some unrecognized configuration lines are not uncommon for new networks, and it is often fine to proceed with further analysis.
 ]
 }
}

我们可以忽略我们收到的警告,因为它不会影响我们的分析。

从 Batfish 收集网络事实

Batfish 可以生成一个代表从提供给 Batfish 的配置文件中发现的关键事实的供应商中立的数据模型。在本篇文章中,我们将概述如何收集 Batfish 发现的这些事实,并如何使用这些信息来验证设备的网络配置是否符合预期状态。

准备工作

网络配置已经生成,并且网络快照已经与 Batfish 服务器同步。

如何做...

  1. 使用以下任务更新pb_batfish_analyis.yml剧本,以收集 Batfish 生成的事实:
 - name: Retrieve Batfish Facts
 bf_extract_facts:
 output_directory: "{{ batfish_analysis_folder }}/bf_facts"
 run_once: yes
 register: bf_facts
  1. 使用以下任务更新pb_batfish_analysis.yml剧本,以验证生成的接口配置:
 - name: Validate all Interfaces are Operational and Have correct IP
 assert:
 that:
 - bf_facts.result.nodes[inventory_hostname].Interfaces[item.port].Active
== true
 - bf_facts.result.nodes[inventory_hostname].Interfaces[item.port].Primary_Address ==
 item.ip + '/' + global.p2p_prefix | string
 loop: "{{ p2p_ip[inventory_hostname] }}"

工作原理...

Batfish 处理网络快照(设备配置)并为配置的不同部分生成供应商中立的数据模型。这些被认为是 Batfish 从输入配置文件生成并收集的事实。我们使用bf_extract_facts Ansible 模块来提取这些事实,然后可以将其保存到一个目录以供进一步分析。

在我们的情况下,我们将 Batfish 分析保存在bf_facts文件夹中,并且该模块生成了一个唯一的 YAML 文件,其中包含了每个设备的这个中立数据模型。以下代码段概述了我们样本拓扑中一个设备(leaf01)的接口数据模型:

nodes:
 leaf01:
 Interfaces:
 Ethernet8:
 Active: true
 All_Prefixes:
 - 172.31.1.1/31
 < --- Output Omitted for brevity --->
 Declared_Names:
 - Ethernet8
 Description: '"DC1 | Rpeer: spine01 | Rport: Ethernet1"'
 < --- Output Omitted for brevity --->
 MTU: 1500
 < --- Output Omitted for brevity --->
 Primary_Address: 172.31.1.1/31
 Primary_Network: 172.31.1.0/31
 < --- Output Omitted for brevity --->
 Speed: 1000000000.0

该模块返回相同的数据结构,我们将这个结果保存在一个名为bf_facts的新变量中。我们使用这个变量中的数据来验证我们生成的配置的设备的预期网络状态。我们还使用assert模块来循环遍历我们数据模型中声明的每个节点的所有接口。然后,我们比较 Batfish 生成的数据模型中这些参数的值,以确保我们所有的接口都是可操作的,并且所有的 IP 地址都配置正确。

还有更多...

Batfish 还提供了不同的内置assert测试,以对其生成的数据模型执行验证。这使其能够对可能影响网络的关键问题提供更简单和更健壮的验证。以下是使用 Batfish 已有的内置assert的任务:

- name: Validate BGP Sessions and Undefined References
 bf_assert:
 assertions:
 - type: assert_no_undefined_references
        name: Confirm we have no undefined references
 - type: assert_no_incompatible_bgp_sessions
 name: Confirm we have no incompatible BGP sessions
 run_once: yes

我们可以在上述代码块中看到两个断言:

  • assert_no_undefined_references:这个断言验证所有配置块是否存在且有效。例如,所有前缀列表都存在,没有未定义引用到缺失的前缀列表。这确保生成的配置是合理的,不包括对未声明对象的未定义引用。

  • Assert_no_incompatible_bgp_sessions:这个断言验证所有 BGP 会话是否正确配置,并且 BGP 对等体的配置之间没有不匹配。这也确保生成的配置是有效的,生成的 BGP 会话将是可操作的。

如果我们需要验证这些测试是否会捕捉到配置中的错误,我们可以通过关闭主配置文件中叶子和脊柱交换机之间的链路来进行验证,如下面的代码块所示:

$ cat configs/leaf01.cfg

!
interface Ethernet8
 description "DC1 | Rpeer: spine01 | Rport: Ethernet1"
 no switchport
 *shutdown*   ip address 172.31.1.1/31
!

此配置更改应该使leaf01spine01节点之间的底层 BGP 会话中断。

当我们再次运行我们的剧本时,将会看到以下错误消息:

TASK [Validate BGP Sessions and Undefined References] ****************************************************************************************************        "result": [

 {
 "details": "Assertion passed",
 "name": "Confirm we have no undefined references",
 "status": "Pass",
 "type": "assert_no_undefined_references"
 },
 {
 "details": "Found incompatible BGP session(s), when none were expected\n[{'Node': 'leaf01', 'VRF': 'default', 'Local_AS': 65001, 'Local_Interface': None, 'Local_IP': '172.31.1.1', 'Remote_AS': '65100', 'Remote_Node': None, 'Remote_Interface': None, 'Remote_IP': '172.31.1.0', 'Session_Type': 'EBGP_SINGLEHOP', 'Configured_Status': 'INVALID_LOCAL_IP'}]",
 "name": "Confirm we have no incompatible BGP sessions",
 "status": "Fail",
 "type": "assert_no_incompatible_bgp_sessions"
 }
 ],
 "summary": "1 of 2 assertions failed"
 }

从输出中,我们可以看到第一个断言成功了,这意味着我们的配置中没有未定义的引用。然而,第二个断言失败了,因为现在有一个 BGP 会话失败了。

另请参阅...

有关 Batfish Ansible 模块支持的所有可用断言的更多信息,请查看以下链接:

使用 Batfish 验证流量转发

在本教程中,我们将概述如何验证网络中的流量转发。这是通过 Batfish 使用从设备配置生成的转发表来实现的。在进行任何更改之前,验证网络内的正确流量转发非常有用。

准备工作

网络配置已经生成,并且网络快照已经与 Batfish 服务器同步。

如何做...

  1. 使用以下任务更新pb_batfish_analyis.yml剧本,以验证我们拓扑中的流量转发:
- name: Validate Traffic Forwarding in the Fabric
 bf_assert:
 assertions:
 - type: assert_all_flows_succeed
 name: confirm host is reachable for traffic received
 parameters:
 startLocation: "{{ item.0 }}"
 headers:
 dstIps: "{{ item.1.value.ip }}"
 srcIps: "{{ lo_ip[item.0].ip }}"
 with_nested:
 - "{{ play_hosts }}"
 - "{{ lo_ip | dict2items }}"
 run_once: yes

工作原理...

Batfish 提供了一种内置的验证方法,用于验证网络拓扑内端点之间的正确流量转发。这是通过使用assert_all_flows_succeed方法实现的。该方法验证给定端点之间的所有流量是否成功。为了使 Batfish 验证任何给定流的流量流动,我们需要提供以下信息:

  • 开始节点位置

  • 流的源 IP

  • 流的目标 IP 地址

Batfish 将使用其生成的数据模型为网络拓扑中的所有节点构建转发表,并验证我们正在测试的流量是否在网络中转发。

在我们的示例拓扑中,我们希望验证所有节点的回环 IP 地址的所有流量是否可以到达所有远程节点的目标回环 IP 地址。我们使用with_nested循环结构来循环遍历我们清单中的所有节点,并在lo_ip数据结构中循环遍历所有回环 IP 地址。这将测试我们清单中的所有节点是否可以到达其他所有节点的远程回环。

当我们运行这个测试时,我们会发现除了从spine01spine02的流量和从spine02spine01的反向流量之外,所有流量都正常工作,如下面的代码块所示:

*### Traffic from Spine01 to Spine02 Failing

*                "msg": "1 of 1 assertions failed",
 "result": [
 {
 "details": "Found a flow that failed, when expected to succeed\n[{'Flow': Flow(dscp=0, dstIp='10.100.1.253', dstPort=0, ecn=0, fragmentOffset=0, icmpCode=0, icmpVar=8, ingressInterface=None, ingressNode='spine01', ingressVrf='default', ipProtocol='ICMP', packetLength=0, srcIp='10.100.1.254', srcPort=0, state='NEW', tag='BASE', tcpFlagsAck=0, tcpFlagsCwr=0, tcpFlagsEce=0, tcpFlagsFin=0, tcpFlagsPsh=0, tcpFlagsRst=0, tcpFlagsSyn=0, tcpFlagsUrg=0), 'Traces': ListWrapper([((ORIGINATED(default), NO_ROUTE))]), 'TraceCount': 1}]",
 "name": "confirm host is reachable for traffic received",
 "status": "Fail",
 "type": "assert_all_flows_succeed"
 }
 ],
 "summary": "1 of 1 assertions failed"
 }

在实时网络中,我们可以检查实时节点上的路由,以验证我们从 Batfish 得出的发现:

dc1-spine01#sh ip route 10.100.1.253

VRF: default
Codes: C - connected, S - static, K - kernel,
 O - OSPF, IA - OSPF inter area, E1 - OSPF external type 1,
 E2 - OSPF external type 2, N1 - OSPF NSSA external type 1,
 N2 - OSPF NSSA external type2, B I - iBGP, B E - eBGP,
 R - RIP, I L1 - IS-IS level 1, I L2 - IS-IS level 2,
 O3 - OSPFv3, A B - BGP Aggregate, A O - OSPF Summary,
 NG - Nexthop Group Static Route, V - VXLAN Control Service,
 DH - Dhcp client installed default route

Gateway of last resort is not set

在检查我们的网络配置后,我们可以看到前面的输出是正确的。这是可能的,因为我们在所有leaf交换机上使用路由映射,只广播本地环回 IP 地址,并且我们不会重新广播来自leaf节点的任何其他 IP 地址。

此外,spine节点之间没有 BGP 会话,因此它们之间没有流量路径。因此,为了完成我们的测试并使其成功,我们将仅测试所有源自leaf节点到所有目的地的流量。

我们不会测试源自spine节点的流量。在这里,您可以看到修改后的任务:

- bf_assert:

    assertions:
      - type: assert_all_flows_succeed
        name: confirm host is reachable for traffic received
        parameters:
          startLocation: "{{ item.0 }}"
          headers:
            dstIps: "{{ item.1.value.ip }}"
            srcIps: "{{ lo_ip[item.0].ip }}"
    with_nested:
      - "{{ play_hosts }}"
      - "{{ lo_ip | dict2items }}"
    when: '"spine" not in item.0'
    run_once: yes

再次运行测试后,所有流量都通过了,任务成功。

使用 Batfish 验证 ACL

在本教程中,我们将概述如何使用 Batfish 验证 ACL 条目,并验证这些 ACL 定义的正确流量处理。这使我们能够将 Batfish 和 Ansible 作为审计工具,强制执行基础设施的正确安全合规性。

准备工作

设备配置已生成,并且网络快照已打包,如前面的教程所述。

操作步骤…

  1. 使用以下 ACL 条目更新leaf03leaf04上的网络配置以保护 Web 虚拟局域网VLAN):
!
ip access-list WEB_VLAN_IN
 10 deny ip host 172.20.10.10 any
 20 permit tcp 172.20.10.0/24 any eq https

!
ip access-list WEB_VLAN_OUT
 10 permit tcp any 172.20.10.0/24 eq https
!
  1. 使用以下任务更新pb_batfish_analyis.yml playbook,以验证我们 Web VLAN 的正确出口 ACL 行为:
- name: Validate Internet to Web Servers
 bf_assert:
 assertions:
 - type: assert_filter_permits
 name: Confirm Internet Access to Web Servers
 parameters:
 filters: "{{ web_acl }}"
 headers:
 dstIps: "{{ web_server_subnet}}"
 srcIps: "0.0.0.0/0"
 dstPorts: '443'
 ipProtocols: 'TCP'
 vars:
 web_acl: WEB_VLAN_OUT
 web_server_subnet: 172.20.10.0/24
 run_once: yes
  1. 使用以下任务更新pb_batfish_analyis.yml playbook,以验证我们 VLAN 的正确入口 ACL 行为:
- name: Validate Server {{ web_server }} is Denied
 bf_assert:
 assertions:
 - type: assert_filter_denies
 name: Confirm Traffic is Denied
 parameters:
 filters: "{{ web_acl_in }}"
 headers:
 dstIps: "0.0.0.0/0"
 srcIps: "{{ web_server}}"
 vars:
 web_acl_in: WEB_VLAN_IN
 web_server: 172.20.10.10
 run_once: yes

工作原理…

Batfish 是另一个用于验证 ACL 处理流量的强大工具。这使我们能够验证特定 ACL 是否允许或拒绝特定流量。Batfish 还提供了一个强大的工具,用于验证涉及 ACL 的网络更改。此外,它可以用作防范实施可能影响网络上的实时流量或导致违反安全策略的恶意 ACL 更改的保障。

我们再次使用bf_assert Batfish 模块,但是在这种情况下,用于验证 ACL。我们使用了该模块中实现的另外两个assert方法,如下所示:

  • assert_filter_permits方法测试并验证 ACL 正确允许特定流量。

  • assert_filter_denies方法测试并验证 ACL 拒绝特定流量。

在我们的 playbook 中,我们创建了两个单独的任务。第一个任务使用assert_filter_permits方法验证从互联网到我们的 Web 服务器子网的所有流量是否被允许。我们使用headers参数来指定要验证的所有流量的 IP 头信息。

然后,我们创建第二个任务,使用assert_filter_denies方法,测试特定 Web 服务器是否被阻止与任何目的地通信。

当我们再次运行我们的 playbook 时,我们可以看到所有任务都成功完成,这表明我们示例网络中 ACL 的行为符合预期。

为了验证我们的过滤器是否工作正常,我们将通过允许超文本传输安全协议HTTPS)流量到被拒绝的 Web 服务器(172.20.10.10)来引入 ACL 过滤器的问题,如下面的代码片段所示:

!
ip access-list WEB_VLAN_IN
 05 permit tcp host 172.20.10.10 any eq ssh
 10 deny ip host 172.20.10.10 any
 20 permit tcp 172.20.10.0/24 any eq https
!

当我们再次运行我们的 playbook 时,我们可以看到最后一个任务出现错误。这个错误显示特定流量流被允许,而预期应该被 ACL 拒绝,如下面的代码块所示:

 "result": [
 {
 "details": "Found a flow that was permitted, when expected to be denied\n[{'Node': 'leaf03', 'Filter_Name': 'WEB_VLAN_IN', 'Flow': Flow(dscp=0, dstIp='0.0.0.0', dstPort=22, ecn=0, fragmentOffset=0, icmpCode=0, icmpVar=0, ingressInterface=None, ingressNode='leaf03', ingressVrf='default', ipProtocol='TCP', packetLength=0, srcIp='172.20.10.10', srcPort=0, state='NEW', tag='BASE', tcpFlagsAck=0, tcpFlagsCwr=0, tcpFlagsEce=0, tcpFlagsFin=0, tcpFlagsPsh=0, tcpFlagsRst=0, tcpFlagsSyn=0, tcpFlagsUrg=0), 'Action': 'PERMIT', 'Line_Content': '05 permit tcp host 172.20.10.10 any eq ssh', 'Trace': AclTrace(events=[AclTraceEvent(class_name='org.batfish.datamodel.acl.PermittedByIpAccessListLine', description='Flow permitted by extended ipv4 access-list named WEB_VLAN_IN, index 0: 05 permit tcp host 172.20.10.10 any eq ssh', lineDescription='05 permit tcp host 172.20.10.10 any eq ssh')])}]",
 "name": "Confirm Traffic is Denied",
 "status": "Fail",
 "type": "assert_filter_denies"
 }
 ]

这个简单的例子表明,我们可以创建更复杂的断言规则,以强制执行网络内的正确安全策略。此外,我们可以利用 Batfish 来验证网络范围内该策略的正确执行。