你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程:使用 Azure PowerShell 保护虚拟中心

本教程将使用一个区域中的虚拟中心创建一个虚拟 WAN 实例,并在该虚拟中心部署 Azure 防火墙以确保连接安全。 此示例将演示虚拟网络之间的安全连接。 虚拟网络和站点到站点、点到站点或 ExpressRoute 分支之间的流量也受虚拟安全中心支持。

在本教程中,你将了解如何执行以下操作:

  • 部署虚拟 WAN
  • 部署 Azure 防火墙并配置自定义路由
  • 测试连接

重要

虚拟 WAN 是中心以及其中提供的服务的集合。 可以部署所需的任意数量的虚拟 WAN。 虚拟 WAN 中心包含多个服务,例如 VPN、ExpressRoute 等。 如果相应区域支持可用性区域,则每个服务(Azure 防火墙除外)都将跨可用性区域自动部署。 若要将现有 Azure 虚拟 WAN 中心升级为安全中心,并让 Azure 防火墙使用可用性区域,必须使用 Azure PowerShell,具体方法将在本文后面部分介绍。

先决条件

登录 Azure

Connect-AzAccount
Select-AzSubscription -Subscription "<sub name>"

初始虚拟 WAN 部署

第一步,需要设置一些变量并创建资源组、虚拟 WAN 实例和虚拟中心:

# Variable definition
$RG = "vwan-rg"
$Location = "westeurope"
$VwanName = "vwan"
$HubName =  "hub1"
$FirewallTier = "Standard" # or "Premium"

# Create Resource Group, Virtual WAN and Virtual Hub
New-AzResourceGroup -Name $RG -Location $Location
$Vwan = New-AzVirtualWan -Name $VwanName -ResourceGroupName $RG -Location $Location -AllowVnetToVnetTraffic -AllowBranchToBranchTraffic -VirtualWANType "Standard"
$Hub = New-AzVirtualHub -Name $HubName -ResourceGroupName $RG -VirtualWan $Vwan -Location $Location -AddressPrefix "192.168.1.0/24" -Sku "Standard"

创建两个虚拟网络并将其作为分支连接到中心:

# Create Virtual Network
$Spoke1 = New-AzVirtualNetwork -Name "spoke1" -ResourceGroupName $RG -Location $Location -AddressPrefix "10.1.1.0/24"
$Spoke2 = New-AzVirtualNetwork -Name "spoke2" -ResourceGroupName $RG -Location $Location -AddressPrefix "10.1.2.0/24"
# Connect Virtual Network to Virtual WAN
$Spoke1Connection = New-AzVirtualHubVnetConnection -ResourceGroupName $RG -ParentResourceName  $HubName -Name "spoke1" -RemoteVirtualNetwork $Spoke1 -EnableInternetSecurityFlag $True
$Spoke2Connection = New-AzVirtualHubVnetConnection -ResourceGroupName $RG -ParentResourceName  $HubName -Name "spoke2" -RemoteVirtualNetwork $Spoke2 -EnableInternetSecurityFlag $True

这样就产生了一个功能齐全的虚拟 WAN,可提供任意连接性。 为了增强安全性,需要将 Azure 防火墙部署到每个虚拟中心。 防火墙策略可用于有效地管理虚拟 WAN Azure 防火墙实例。 所以在此示例中还创建了防火墙策略:

# New Firewall Policy
$FWPolicy = New-AzFirewallPolicy -Name "VwanFwPolicy" -ResourceGroupName $RG -Location $Location
# New Firewall Public IP
$AzFWPIPs = New-AzFirewallHubPublicIpAddress -Count 1
$AzFWHubIPs = New-AzFirewallHubIpAddress -PublicIP $AzFWPIPs
# New Firewall
$AzFW = New-AzFirewall -Name "azfw1" -ResourceGroupName $RG -Location $Location `
            -VirtualHubId $Hub.Id -FirewallPolicyId $FWPolicy.Id `
            -SkuName "AZFW_Hub" -HubIPAddress $AzFWHubIPs `
            -SkuTier $FirewallTier

注意

以下防火墙创建命令不使用可用性区域。 如果你想要使用此功能,需要添加一个附加参数 -Zone。 本文末尾的“升级”部分提供了示例。

启用从 Azure 防火墙到 Azure Monitor 的日志记录是一项可选操作,但在此示例中,将使用防火墙日志来证明流量正在遍历防火墙:

# Optionally, enable logging of Azure Firewall to Azure Monitor
$LogWSName = "vwan-" + (Get-Random -Maximum 99999) + "-" + $RG
$LogWS = New-AzOperationalInsightsWorkspace -Location $Location -Name $LogWSName -Sku Standard -ResourceGroupName $RG
Set-AzDiagnosticSetting -ResourceId $AzFW.Id -Enabled $True -Category AzureFirewallApplicationRule, AzureFirewallNetworkRule -WorkspaceId $LogWS.ResourceId

部署 Azure 防火墙并配置自定义路由

注意

这是在使用 Azure 防火墙管理器保护来自 Azure 门户的连接并且“中心间”设置设为“已禁用”时部署的配置。 有关在“中心间”设置为“已启用”时如何使用 powershell 配置路由的说明,请参阅启用路由意向

现在中心已具有 Azure 防火墙,但仍需要修改路由,以便让虚拟 WAN 通过防火墙发送来自虚拟网络和分支的流量。 可以通过两个步骤达到此目的:

  1. 配置要传播到 None 路由表的所有虚拟网络连接(以及存在的所有分支连接)。 此配置的效果是,其他虚拟网络和分支不会学习其前缀,因此没有路由来访问它们。
  2. 现在可以在 Default 路由表(默认情况下关联所有虚拟网络和分支)中插入静态路由,以便将所有流量发送到 Azure 防火墙。

从第一步开始,将虚拟网络连接配置为传播到 None 路由表:

# Configure Virtual Network connections in hub to propagate to None
$VnetRoutingConfig = $Spoke1Connection.RoutingConfiguration    # We take $Spoke1Connection as baseline for the future vnet config, all vnets will have an identical config
$NoneRT = Get-AzVhubRouteTable -ResourceGroupName $RG -HubName $HubName -Name "noneRouteTable"
$NewPropRT = @{}
$NewPropRT.Add('Id', $NoneRT.Id)
$PropRTList = @()
$PropRTList += $NewPropRT
$VnetRoutingConfig.PropagatedRouteTables.Ids = $PropRTList
$VnetRoutingConfig.PropagatedRouteTables.Labels = @()
$Spoke1Connection = Update-AzVirtualHubVnetConnection -ResourceGroupName $RG -ParentResourceName  $HubName -Name "spoke1" -RoutingConfiguration $VnetRoutingConfig
$Spoke2Connection = Update-AzVirtualHubVnetConnection -ResourceGroupName $RG -ParentResourceName  $HubName -Name "spoke2" -RoutingConfiguration $VnetRoutingConfig

现在可以继续执行第二步,将静态路由添加到 Default 路由表。 此示例应用 Azure 防火墙管理器在保护虚拟 WAN 中的连接时生成的默认配置,但可以更改静态路由中的前缀列表以满足特定要求:

# Create static routes in default Route table
$AzFWId = $(Get-AzVirtualHub -ResourceGroupName $RG -name  $HubName).AzureFirewall.Id
$AzFWRoute = New-AzVHubRoute -Name "all_traffic" -Destination @("0.0.0.0/0", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16") -DestinationType "CIDR" -NextHop $AzFWId -NextHopType "ResourceId"
$DefaultRT = Update-AzVHubRouteTable -Name "defaultRouteTable" -ResourceGroupName $RG -VirtualHubName  $HubName -Route @($AzFWRoute)

注意

字符串“all_traffic”作为上述 New-AzVHubRoute 命令中参数“-Name”的值具有特殊含意:如果原封不动地使用这个字符串,则会在 Azure 门户中正确反映本文中应用的配置(防火墙管理器 --> 虚拟中心 --> [你的中心] --> 安全配置)。 如果使用另一名称,则会应用所需的配置,但该配置不会反映在 Azure 门户中。

启用路由意向

如果要通过虚拟 WAN 中心内部署的 Azure 防火墙发送中心间和区域间流量,可以改为启用路由意向功能。 有关路由意向的详细信息,请参阅路由意向文档

注意

这是在使用 Azure 防火墙管理器保护来自 Azure 门户的连接并且“中心间”设置设为“已启用”时部署的配置。

# Get the Azure Firewall resource ID
$AzFWId = $(Get-AzVirtualHub -ResourceGroupName <thname> -name  $HubName).AzureFirewall.Id

# Create routing policy and routing intent
$policy1 = New-AzRoutingPolicy -Name "PrivateTraffic" -Destination @("PrivateTraffic") -NextHop $firewall.Id
$policy2 = New-AzRoutingPolicy -Name "PublicTraffic" -Destination @("Internet") -NextHop $firewall.Id
New-AzRoutingIntent -ResourceGroupName "<rgname>" -VirtualHubName "<hubname>" -Name "hubRoutingIntent" -RoutingPolicy @($policy1, $policy2)

如果在虚拟网络或本地使用虚拟 WAN 中的非 RFC1918 前缀(例如 40.0.0.0/24),请在路由意向配置完成后,在 defaultRouteTable 中添加其他路由。 请确保将此路由命名为 private_traffic。 如果以其他方式命名路由,虽然会应用所需的配置,但它不会反映在 Azure 门户中。

# Get the defaultRouteTable
$defaultRouteTable = Get-AzVHubRouteTable -ResourceGroupName routingIntent-Demo -HubName wus_hub1 -Name defaultRouteTable

# Get the routes automatically created by routing intent. If private routing policy is enabled, this is the route named _policy_PrivateTraffic. If internet routing policy is enabled, this is the route named _policy_InternetTraffic. 
$privatepolicyroute = $defaultRouteTable.Routes[1]


# Create new route named private_traffic for non-RFC1918 prefixes
$private_traffic = New-AzVHubRoute -Name "private-traffic" -Destination @("30.0.0.0/24") -DestinationType "CIDR" -NextHop $AzFWId -NextHopType ResourceId

# Create new routes for route table
$newroutes = @($privatepolicyroute, $private_traffic)

# Update route table
Update-AzVHubRouteTable -ResourceGroupName <rgname> -ParentResourceName <hubname> -Name defaultRouteTable -Route $newroutes

测试连接

现在,你已具有功能正常的安全中心。 若要测试连接性,需要在连接到中心的每个分支虚拟网络中都具有一个虚拟机:

# Create VMs in spokes for testing
$VMLocalAdminUser = "lab-user"
$VMLocalAdminSecurePassword = ConvertTo-SecureString -AsPlainText -Force
$VMCredential = New-Object System.Management.Automation.PSCredential ($VMLocalAdminUser, $VMLocalAdminSecurePassword);
$VMSize = "Standard_B2ms"
# Spoke1
$Spoke1 = Get-AzVirtualNetwork -ResourceGroupName $RG -Name "spoke1"
Add-AzVirtualNetworkSubnetConfig -Name "vm" -VirtualNetwork $Spoke1 -AddressPrefix "10.1.1.0/26"
$Spoke1 | Set-AzVirtualNetwork
$VM1 = New-AzVM -Name "spoke1-vm" -ResourceGroupName $RG -Location $Location `
            -Image "UbuntuLTS" -credential $VMCredential `
            -VirtualNetworkName "spoke1" -SubnetName "vm" -PublicIpAddressName "spoke1-pip"
$NIC1 = Get-AzNetworkInterface -ResourceId $($VM1.NetworkProfile.NetworkInterfaces[0].Id)
$Spoke1VMPrivateIP = $NIC1.IpConfigurations[0].PrivateIpAddress
$Spoke1VMPIP = $(Get-AzPublicIpAddress -ResourceGroupName $RG -Name "spoke1-pip")
# Spoke2
$Spoke2 = Get-AzVirtualNetwork -ResourceGroupName $RG -Name "spoke2"
Add-AzVirtualNetworkSubnetConfig -Name "vm" -VirtualNetwork $Spoke2 -AddressPrefix "10.1.2.0/26"
$Spoke2 | Set-AzVirtualNetwork
$VM2 = New-AzVM -Name "spoke2-vm" -ResourceGroupName $RG -Location $Location `
            -Image "UbuntuLTS" -credential $VMCredential `
            -VirtualNetworkName "spoke2" -SubnetName "vm" -PublicIpAddressName "spoke2-pip"
$NIC2 = Get-AzNetworkInterface -ResourceId $($VM2.NetworkProfile.NetworkInterfaces[0].Id)
$Spoke2VMPrivateIP = $NIC2.IpConfigurations[0].PrivateIpAddress
$Spoke2VMPIP = $(Get-AzPublicIpAddress -ResourceGroupName $RG -Name "spoke2-pip")

防火墙策略中的默认配置是删除所有内容。 因此需要配置一些规则。 首先是配置 DNAT 规则,以便能通过防火墙的公共 IP 地址访问测试虚拟机:

# Adding DNAT rules for virtual machines in the spokes
$AzFWPublicAddress = $AzFW.HubIPAddresses.PublicIPs.Addresses[0].Address
$NATRuleSpoke1 = New-AzFirewallPolicyNatRule -Name "Spoke1SSH" -Protocol "TCP" `
        -SourceAddress "*" -DestinationAddress $AzFWPublicAddress -DestinationPort 10001 `
        -TranslatedAddress $Spoke1VMPrivateIP -TranslatedPort 22
$NATRuleSpoke2 = New-AzFirewallPolicyNatRule -Name "Spoke2SSH" -Protocol "TCP" `
        -SourceAddress "*" -DestinationAddress $AzFWPublicAddress -DestinationPort 10002 `
        -TranslatedAddress $Spoke2VMPrivateIP -TranslatedPort 22
$NATCollection = New-AzFirewallPolicyNatRuleCollection -Name "SSH" -Priority 100 `
        -Rule @($NATRuleSpoke1, $NATRuleSpoke2) -ActionType "Dnat"
$NATGroup = New-AzFirewallPolicyRuleCollectionGroup -Name "NAT" -Priority 100 -RuleCollection $NATCollection -FirewallPolicyObject $FWPolicy

现在可以配置一些示例规则。 定义允许 SSH 流量的网络规则,以及允许通过 Internet 访问完全限定的域名 ifconfig.co 的应用程序规则。 此 URL 返回它在 HTTP 请求中获取的源 IP 地址:

# Add Network Rule
$SSHRule = New-AzFirewallPolicyNetworkRule -Name PermitSSH -Protocol TCP `
        -SourceAddress "10.0.0.0/8" -DestinationAddress "10.0.0.0/8" -DestinationPort 22
$NetCollection = New-AzFirewallPolicyFilterRuleCollection -Name "Management" -Priority 100 -ActionType Allow -Rule $SSHRule
$NetGroup = New-AzFirewallPolicyRuleCollectionGroup -Name "Management" -Priority 200 -RuleCollection $NetCollection -FirewallPolicyObject $FWPolicy
# Add Application Rule
$ifconfigRule = New-AzFirewallPolicyApplicationRule -Name PermitIfconfig -SourceAddress "10.0.0.0/8" -TargetFqdn "ifconfig.co" -Protocol "http:80","https:443"
$AppCollection = New-AzFirewallPolicyFilterRuleCollection -Name "TargetURLs" -Priority 300 -ActionType Allow -Rule $ifconfigRule
$NetGroup = New-AzFirewallPolicyRuleCollectionGroup -Name "TargetURLs" -Priority 300 -RuleCollection $AppCollection -FirewallPolicyObject $FWPolicy

在实际发送任何流量之前,可以检查虚拟机的有效路由。 它们应包含从虚拟 WAN(0.0.0.0/0 加 RFC1918)获知的前缀,但不包含其他分支的前缀:

# Check effective routes in the VM NIC in spoke 1
# Note that 10.1.2.0/24 (the prefix for spoke2) should not appear
Get-AzEffectiveRouteTable -ResourceGroupName $RG -NetworkInterfaceName $NIC1.Name | ft
# Check effective routes in the VM NIC in spoke 2
# Note that 10.1.1.0/24 (the prefix for spoke1) should not appear
Get-AzEffectiveRouteTable -ResourceGroupName $RG -NetworkInterfaceName $NIC2.Name | ft

现在,生成从一个虚拟机到另一个虚拟机的流量,然后验证 Azure 防火墙中是否已删除该流量。 在以下 SSH 命令中,需要接受虚拟机指纹,并提供在创建虚拟机时定义的密码。 在此示例中,将从 spoke1 中的虚拟机向 spoke2 发送五个 ICMP 回显请求数据包,并使用 Linux 实用工具 nc 在端口 22 上尝试 TCP 连接(使用 -vz 标志,只发送连接请求并显示结果)。 结果应为 ping 失败,而端口 22 上的 TCP 连接尝试成功,因为之前配置的网络规则允许该连接:

# Connect to one VM and ping the other. It should not work, because the firewall should drop the traffic, since no rule for ICMP is configured
ssh $AzFWPublicAddress -p 10001 -l $VMLocalAdminUser "ping $Spoke2VMPrivateIP -c 5"
# Connect to one VM and send a TCP request on port 22 to the other. It should work, because the firewall is configured to allow SSH traffic (port 22)
ssh $AzFWPublicAddress -p 10001 -l $VMLocalAdminUser "nc -vz $Spoke2VMPrivateIP 22"

还可以验证 Internet 流量。 通过实用工具 curl 对防火墙策略 (ifconfig.co) 中允许的 FQDN 的 HTTP 请求应成功,但对任何其他目标的 HTTP 请求应会失败(本示例使用 bing.com 进行测试):

# This HTTP request should succeed, since it is allowed in an app rule in the AzFW, and return the public IP of the FW
ssh $AzFWPublicAddress -p 10001 -l $VMLocalAdminUser "curl -s4 ifconfig.co"
# This HTTP request should fail, since the FQDN bing.com is not in any app rule in the firewall policy
ssh $AzFWPublicAddress -p 10001 -l $VMLocalAdminUser "curl -s4 bing.com"

若要验证防火墙是否删除了数据包,最简单的方法是检查日志。 由于已将 Azure 防火墙配置为将日志发送到 Azure Monitor,因此可以使用 Kusto 查询语言从 Azure Monitor 检索相关日志:

注意

可能需要大约 1 分钟的时间才能看到日志被发送到 Azure Monitor

# Getting Azure Firewall network rule Logs
$LogWS = Get-AzOperationalInsightsWorkspace -ResourceGroupName $RG
$LogQuery = 'AzureDiagnostics
| where Category == "AzureFirewallNetworkRule"
| where TimeGenerated >= ago(5m)
| parse msg_s with Protocol " request from " SourceIP ":" SourcePortInt:int " to " TargetIP ":" TargetPortInt:int *
| parse msg_s with * ". Action: " Action1a
| parse msg_s with * " was " Action1b " to " NatDestination
| parse msg_s with Protocol2 " request from " SourceIP2 " to " TargetIP2 ". Action: " Action2
| extend SourcePort = tostring(SourcePortInt),TargetPort = tostring(TargetPortInt)
| extend Action = case(Action1a == "", case(Action1b == "",Action2,Action1b), Action1a),Protocol = case(Protocol == "", Protocol2, Protocol),SourceIP = case(SourceIP == "", SourceIP2, SourceIP),TargetIP = case(TargetIP == "", TargetIP2, TargetIP),SourcePort = case(SourcePort == "", "N/A", SourcePort),TargetPort = case(TargetPort == "", "N/A", TargetPort),NatDestination = case(NatDestination == "", "N/A", NatDestination)
| project TimeGenerated, Protocol, SourceIP,SourcePort,TargetIP,TargetPort,Action, NatDestination, Resource
| take 25 '
$(Invoke-AzOperationalInsightsQuery -Workspace $LogWS -Query $LogQuery).Results | ft

在前面的命令中,应看到不同的条目:

  • 正在对 SSH 连接应用 DNAT 规则
  • 已删除分支(10.1.1.4 和 10.1.2.4)中 VM 之间的 ICMP 数据包
  • 允许在分支中的 VM 之间建立 SSH 连接

下面是上述命令生成的输出示例:

TimeGenerated            Protocol    SourceIP       SourcePort TargetIP      TargetPort Action  NatDestination Resource
-------------            --------    --------       ---------- --------      ---------- ------  -------------- --------
2020-10-04T20:53:02.41Z  TCP         109.125.122.99 62281      51.105.224.44 10001      DNAT'ed 10.1.1.4:22    AZFW1
2020-10-04T20:53:07.045Z TCP         10.1.1.4       35932      10.1.2.4      22         Allow   N/A            AZFW1
2020-10-04T20:53:50.119Z TCP         109.125.122.99 62293      51.105.224.44 10001      DNAT'ed 10.1.2.4:22    AZFW1
2020-10-04T20:52:47.475Z TCP         109.125.122.99 62273      51.105.224.44 10001      DNAT'ed 10.1.2.4:22    AZFW1
2020-10-04T20:51:04.682Z TCP         109.125.122.99 62200      51.105.224.44 10001      DNAT'ed 10.1.2.4:22    AZFW1
2020-10-04T20:51:17.031Z ICMP Type=8 10.1.1.4       N/A        10.1.2.4      N/A        Deny    N/A            AZFW1
2020-10-04T20:51:18.049Z ICMP Type=8 10.1.1.4       N/A        10.1.2.4      N/A        Deny    N/A            AZFW1
2020-10-04T20:51:19.075Z ICMP Type=8 10.1.1.4       N/A        10.1.2.4      N/A        Deny    N/A            AZFW1
2020-10-04T20:51:20.097Z ICMP Type=8 10.1.1.4       N/A        10.1.2.4      N/A        Deny    N/A            AZFW1
2020-10-04T20:51:21.121Z ICMP Type=8 10.1.1.4       N/A        10.1.2.4      N/A        Deny    N/A            AZFW1
2020-10-04T20:52:52.356Z TCP         10.1.1.4       53748      10.1.2.4      22         Allow   N/A            AZFW1

若要查看应用程序规则(描述允许和拒绝的 HTTP 连接)的日志或更改日志显示方式,可以尝试使用其他 KQL 查询。 可以在 Azure 防火墙的 Azure Monitor 日志中找到一些示例。

清理资源

若要删除测试环境,可以删除带有所有包含的对象的资源组:

# Delete resource group and all contained resources
Remove-AzResourceGroup -Name $RG

将具有可用性区域的新 Azure 防火墙部署到现有中心

前面的过程使用 Azure PowerShell 创建新的 Azure 虚拟 WAN 中心,然后立即使用 Azure 防火墙将其转换为安全中心。 可对现有的 Azure 虚拟 WAN 中心运用类似的方法。 防火墙管理器也可用于转换,但不能在没有基于脚本的方法的情况下跨可用性区域部署 Azure 防火墙。 可以使用以下代码片段将现有 Azure 虚拟 WAN 中心转换为安全中心,从而使用在全部三个可用性区域中部署的 Azure 防火墙。

注意

此过程会部署新的 Azure 防火墙。 在没有可用性区域的情况下,无法将现有 Azure 防火墙升级到具有可用性区域的防火墙。 必须先删除中心中的现有 Azure 防火墙,然后使用此过程再次创建它。

# Variable definition
$RG = "vwan-rg"
$Location = "westeurope"
$VwanName = "vwan"
$HubName =  "hub1"
$FirewallName = "azfw1"
$FirewallTier = "Standard" # or "Premium"
$FirewallPolicyName = "VwanFwPolicy"

# Get references to vWAN and vWAN Hub to convert #
$Vwan = Get-AzVirtualWan -ResourceGroupName $RG -Name $VwanName
$Hub = Get-AzVirtualHub -ResourceGroupName  $RG -Name $HubName

# Create a new Firewall Policy #
$FWPolicy = New-AzFirewallPolicy -Name $FirewallPolicyName -ResourceGroupName $RG -Location $Location

# Create a new Firewall Public IP #
$AzFWPIPs = New-AzFirewallHubPublicIpAddress -Count 1
$AzFWHubIPs = New-AzFirewallHubIpAddress -PublicIP $AzFWPIPs

# Create Firewall instance #
$AzFW = New-AzFirewall -Name $FirewallName -ResourceGroupName $RG -Location $Location `
            -VirtualHubId $Hub.Id -FirewallPolicyId $FWPolicy.Id `
            -SkuName "AZFW_Hub" -HubIPAddress $AzFWHubIPs `
            -SkuTier $FirewallTier `
            -Zone 1,2,3

运行此脚本后,可用性区域应显示在安全中心属性中,如以下屏幕截图所示:

安全虚拟中心可用性区域的屏幕截图。

部署 Azure 防火墙后,必须按照前面的“部署 Azure 防火墙并配置自定义路由”部分中所述完成配置过程。

后续步骤