├── .gitignore ├── LICENSE ├── README.md ├── docs └── MH-PANFW.gif ├── scenario1 ├── README.md ├── docs │ ├── panfw_commit.png │ ├── panfw_login.png │ ├── panfw_monitor.png │ ├── scenario1-allowmgmntnsgpubip.png │ ├── scenario1-architecture.png │ ├── spoke01-vm_check_internet_access.png │ └── terraform_apply.png └── templates │ ├── .gitignore │ ├── files │ ├── bootstrap.tpl │ └── init-cfg.txt │ ├── firewall.tf │ ├── main.tf │ ├── network.tf │ ├── output.tf │ ├── provider.tf │ ├── variables.tf │ └── vms.tf ├── scenario2 ├── README.md ├── docs │ ├── scenario2-add-trustvip.png │ ├── scenario2-add-untrustvip.png │ ├── scenario2-allowmgmntnsgpubip.png │ ├── scenario2-architecture-failover.gif │ ├── scenario2-architecture.png │ ├── scenario2-configure-ethernet13-configured-and-up.png │ ├── scenario2-configure-ethernet13-green.png │ ├── scenario2-configure-ethernet13.png │ ├── scenario2-configure-ha-sp.png │ ├── scenario2-configure-panfw01-ha-general.png │ ├── scenario2-configure-panfw01-ha-hacommunications.png │ ├── scenario2-create-sp.png │ ├── scenario2-dashboard-status.png │ ├── scenario2-dashboard.png │ ├── scenario2-device-ha-general.png │ ├── scenario2-passive-appliance-becomes-active.png │ └── terraform_apply.png └── templates │ ├── .gitignore │ ├── files │ ├── bootstrap.tpl │ └── init-cfg.txt │ ├── firewall.tf │ ├── main.tf │ ├── network.tf │ ├── output.tf │ ├── provider.tf │ ├── variables.tf │ └── vms.tf ├── scenario3 ├── README.md ├── docs │ ├── scenario3-allowmgmntnsgpubip.png │ ├── scenario3-architecture.png │ ├── scenario3-check-ha-no-packets-loss.png │ ├── scenario3-check-ha-ping.png │ ├── scenario3-dnat-elb-loadbalancingrule-ssh.png │ ├── scenario3-dnat-nat_rule_fw01-general.png │ ├── scenario3-dnat-nat_rule_fw01-original.png │ ├── scenario3-dnat-nat_rule_fw01-translated.png │ ├── scenario3-dnat-nsg.png │ ├── scenario3-dnat-security_rule_fw01.png │ ├── scenario3-dnat-ssh-success.png │ ├── scenario3-ifconfig-natgw.png │ ├── scenario3-ifconfig-pip.png │ ├── scenario3-natgw-01.png │ ├── scenario3-natgw-02.png │ ├── scenario3-natgw-03.png │ ├── scenario3-natgw-04.png │ └── terraform_apply.png └── templates │ ├── .gitignore │ ├── files │ ├── bootstrap.tpl │ └── init-cfg.txt │ ├── firewall.tf │ ├── lbs.tf │ ├── main.tf │ ├── network.tf │ ├── output.tf │ ├── provider.tf │ ├── variables.tf │ └── vms.tf ├── scenario4 ├── README.md ├── docs │ ├── scenario4-allowmgmntnsgpubip.png │ ├── scenario4-architecture.png │ ├── scenario4-scaling-cpu.png │ ├── scenario4-scaling-cpu2.png │ ├── scenario4-scaling-cpu3.png │ ├── scenario4-scaling-newinstances.png │ ├── scenario4-scaling-newinstances2.png │ ├── scenario4-scaling-policy.png │ ├── scenario4-scaling-runhistory.png │ └── terraform_apply.png └── templates │ ├── .gitignore │ ├── files │ ├── bootstrap.tpl │ └── init-cfg.txt │ ├── firewall.tf │ ├── lbs.tf │ ├── main.tf │ ├── natgw.tf │ ├── network.tf │ ├── output.tf │ ├── provider.tf │ ├── variables.tf │ └── vms.tf └── scenario5 └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ### Terraform ### 2 | **bootstrap*.xml* 3 | 4 | # Local .terraform directories 5 | **/.terraform/* 6 | .terraform.lock.hcl 7 | 8 | # .tfstate files 9 | *.tfstate 10 | *.tfstate.* 11 | 12 | # Crash log files 13 | crash.log 14 | crash.*.log 15 | 16 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 17 | # password, private keys, and other secrets. These should not be part of version 18 | # control as they are data points which are potentially sensitive and subject 19 | # to change depending on the environment. 20 | *.tfvars 21 | *.tfvars.json 22 | 23 | # Ignore override files as they are usually used to override resources locally and so 24 | # are not checked in 25 | override.tf 26 | override.tf.json 27 | *_override.tf 28 | *_override.tf.json 29 | 30 | # Include override files you do wish to add to version control using negated pattern 31 | # !example_override.tf 32 | 33 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 34 | # example: *tfplan* 35 | 36 | # Ignore CLI configuration files 37 | .terraformrc 38 | terraform.rc 39 | 40 | # Ignore Mac .DS_Store files 41 | .DS_Store 42 | 43 | # Ignored vscode files 44 | .vscode/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 David Santiago 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MicroHack - Azure - Palo Alto Networks VM-Series Firewall 2 | 3 | ![](docs/MH-PANFW.gif) 4 | 5 | ## Introduction 6 | 7 | The **Palo Alto Networks VM-Series firewalls** offer robust control and protection for your applications housed within the Azure Cloud. 8 | 9 | There are numerous ways to deploy PAN FW in Azure. This MicroHack is designed to explore the different scenarios that are possible. 10 | 11 | ## Scenarios 12 | 13 | > The deployments in the following scenarios have been designed for **educational purposes**, not for production use. 14 | 15 | 16 | * [Scenario #1: Single instance](scenario1/README.md) 17 | 18 | Deploy a single instance of Palo Alto Firewall for a simple and straightforward protection solution. 19 | 20 | * [Scenario #2: Active-Passive HA](scenario2/README.md) 21 | 22 | Implement a High Availability Firewall with one active and one passive instance. The failover occurs within a few minutes. 23 | 24 | * [Scenario #3: Active-Active loadbalanced with ELB/ILB](scenario3/README.md) 25 | 26 | Opt for a High Availability Firewall with two active instances to distribute the load and minimize the risk of failure. 27 | 28 | * [Scenario #4: Auto-Scaling loadbalanced with ELB/ILB](scenario4/README.md) 29 | 30 | Utilize an auto-scaling Firewall setup that dynamically adjusts the number of active instances based on traffic load. 31 | 32 | * [Scenario #5: Cloud NGFW for Azure](scenario5/README.md) 33 | 34 | Deploy a Next-Generation Firewall for Azure directly in the cloud for advanced threat prevention and secure access control. 35 | 36 | ## Scenarios comparison 37 | 38 | | Feature | Single Instance (#1) | Active-Passive HA (#2) | Active-Active w. ELB/ILB (#3) | Auto-Scaling w. ELB/ILB (#4) | Cloud NGFW for Azure (#5) | 39 | |--------------------------|------------------------|-----------------------------|-----------------------------|----------------------|----------------------------| 40 | | Deployment Complexity | Low | Moderate | Moderate | Moderate | Low | 41 | | High Availability | N/A | Yes *(with ~5min downtime)* | Yes | Yes | Yes | 42 | | Scalability | N/A | N/A | N/A | Yes | Yes | 43 | | Redundancy | No | Yes | Yes | Yes | Yes | 44 | | Traffic Distribution | N/A | N/A | Load balanced between instances | Load balanced between instances | Load balanced between instances | 45 | | Cost | + | ++ | ++ | +++ | +++ | 46 | | Security Features | Standard | Standard | Standard | Standard | [Superior network security features](https://azure.microsoft.com/en-us/updates/public-preview-cloud-next-generation-firewall-for-azure-from-palo-alto-networks/) | 47 | | Management Complexity | Simple | Moderate | Moderate | Moderate | Simple *(managed service)* | 48 | | VPN termination | Yes | Yes | No | No | No | 49 | | BGP peering | Yes | Yes | Yes | No | No | 50 | 51 | ## Appendix 52 | 53 | * [Azure Gateway Load Balancer with PAN FW](https://github.com/vmisson/terraform-azure-gwlb-palo-alto) 54 | * [VM-Series Models on Azure Virtual Machines (VMs)](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-performance-capacity/vm-series-performance-capacity/vm-series-on-azure-models-and-vms) 55 | 56 | ## Contributors ❤️❤️ 57 | 58 | * [Cynthia Treger](https://github.com/cynthiatreger) 59 | * [Vincent Misson](https://github.com/vmisson) 60 | * [Ludovic Rivallain](https://github.com/lrivallain) 61 | 62 | -------------------------------------------------------------------------------- /docs/MH-PANFW.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/docs/MH-PANFW.gif -------------------------------------------------------------------------------- /scenario1/README.md: -------------------------------------------------------------------------------- 1 | ### [<< BACK TO THE MAIN MENU](../README.md) 2 | 3 | # Scenario #1: Single instance 4 | 5 | In this scenarion, we will be deploying a single instance of the VM-Series firewall on Azure. 6 | 7 | ## Overview 8 | 9 | The proposed architecture consists of a single firewall instance set up within the Azure environment. This firewall acts as a control point for your network, overseeing and managing inbound and outbound network traffic based on preconfigured security rules. 10 | 11 | ![img](docs/scenario1-architecture.png) 12 | 13 | Throughout the tasks in this scenario, we will guide you through: 14 | * Deploying the template 15 | * Enabling your public IP for console access, and 16 | * Connecting to the Palo Alto console. 17 | 18 | This will provide you with practical experience in setting up and managing a Palo Alto VM-Series firewall instance on Azure. 19 | 20 | By the end of this scenario, you will have a fully operational Palo Alto firewall running in your Azure environment, ready to provide the necessary security measures for your applications. 21 | 22 | ## Task 1: Deploy Templates 23 | 24 | To begin the Terraform deployment, following these steps: 25 | 26 | - Sign in to Azure Cloud shell at [https://shell.azure.com/](https://shell.azure.com/) or use your local terminal 27 | 28 | - Confirm that you are operating within the appropriate subscription by using: 29 | 30 | `az account show` 31 | 32 | - Accept the Azure Marketplace terms for the VM-Series images: 33 | 34 | `az vm image terms accept --publisher paloaltonetworks --offer vmseries-flex --plan byol --subscription MySubscription` 35 | 36 | - Clone the current GitHub repository with the command: 37 | 38 | `git clone https://github.com/davidsntg/microhack-azure-panfw` 39 | 40 | - Navigate to the new folder *microhack-azure-panfw/* and initialize the terraform modules with the commands: 41 | 42 | `cd microhack-azure-panfw/scenario1/templates` 43 | 44 | `terraform init` 45 | 46 | - Start the deployment by running: 47 | 48 | `terraform apply` 49 | 50 | - When prompted, confirm the start of the deployment by responding with a **yes**. 51 | 52 | - Wait for the deployment to finish, which should take approximately 10 minutes. 53 | 54 | 55 | ## Task 2: Enable your Public IP to Access the Palo Alto Console 56 | 57 | The Palo Alto administration console can be accessed via HTTPS, using the appliance's public management IP. 58 | 59 | During deployment, the public IP from which Terraform is executed provides access to the administration console. 60 | 61 | If this IP differs from the client's public IP accessing the administration console, the NSG `panfw-vm-mgmt-nsg` must be updated: 62 | 63 | ![img](docs/scenario1-allowmgmntnsgpubip.png) 64 | 65 | 66 | ## Task 3: Connect to the Palo Alto Console 67 | 68 | - Open a web browser and navigate to the console. The URL, username and password are given by the results of the previous `terraform apply`: 69 | ![img](docs/terraform_apply.png) 70 | 71 | Run the command `terraform output paloalto_password` to display the password in plain text. 72 | 73 | > **Note**: The Firewall may take between 5-10 minutes to start up. If the console does not appear, feel free to refresh the page. 74 | 75 | ![img](docs/panfw_login.png) 76 | 77 | ## Task 4: Configure the Appliance to Allow VMs Internet Access (Optional) 78 | 79 | > This task has already been accomplished during the firewall provisioning bootstrap process. Nonetheless, understanding how to create a security rule remains of interest. 80 | 81 | * Navigate to the *Policies* tab in the administration console. Under *Policies*, select *Security*. You will find a list of all security rules. 82 | 83 | > You will see that the appliance has been created with two existing rules: 84 | > 85 | > #1: `A-TRUST-TRUST-PING`: This rule allows for pinging between machines that are part of 'trust' zone. 86 | > 87 | > #2: `DenyAll`: This rule denies all that is not explicitly allowed. 88 | 89 | * Create a new security rule by clicking on 'Add'. Configure the rule as follows: 90 | 91 | * Name: A-TRUST-UNTRUST-HTTP_HTTPS 92 | * Description: Allow machines coming from `trust` zone to go on to the Internet 93 | * Source zone: `trust` 94 | * Destination zone: `untrust` 95 | * Service: `service-http` and `service-https` 96 | * Action: Allow 97 | * Log at Session Start: `checked` 98 | * Log at Session End: `checked` 99 | 100 | Click on 'OK' to save the rule 101 | 102 | * To apply new created rule, commit your changes to update the firewall's configuration: 103 | 104 | ![docs](docs/panfw_commit.png) 105 | 106 | Now, the `spoke01-vm` and `spoke02-vm` VMs should have internet access through the VM-Series firewall. Let's test it! 107 | 108 | 109 | ## Task 5: Verify Internet Access for `spoke01-vm` 110 | 111 | After updating the firewall's configuration, it is essential to verify that `spoke01-vm` has proper internet access. 112 | 113 | Follow these steps to confirm: 114 | 115 | * Within the `rg-panfw-scenario1` resource group, locate and select the `spoke01-vm` 116 | * On the left sidebar, click on 'Serial Console' 117 | * Login to the VM using same username and password that was previously used for the Palo Alto administration console 118 | * Execute the following command: `$ curl ifconfig.me` 119 | 120 | ![img](docs/spoke01-vm_check_internet_access.png) 121 | 122 | As demonstrated, `spoke01-vm` has internet access and is using the public IP of the untrust NIC/Interface of the firewall. 123 | 124 | ## Task 6: Monitoring Traffic 125 | 126 | * Navigate to the *Monitor* tab in the administration console: 127 | 128 | ![img](docs/panfw_monitor.png) 129 | 130 | As shown, you can monitor real-time network traffic on the appliance. This functionality is highly valuable, particularly for observing the traffic flow and identifying the rules triggered, which aids in troubleshooting. 131 | 132 | 133 | ## 🏁 Results 134 | 135 | * We successfully deployed a single instance of the VM-Series firewall on Azure. The firewall acted as a control point, managing inbound and outbound network traffic. 136 | * The public IP was enabled for console access and connection to the Palo Alto console was established. This provided hands-on experience in managing a VM-Series firewall. 137 | * New security rules were created and applied to allow spokes VMs internet access. The effectiveness of these rules was verified, ensuring the VMs had proper internet access. 138 | * We explored the real-time network traffic monitoring feature on the appliance. This feature is important for observing traffic flow, identifying triggered rules, and troubleshooting. 139 | 140 | ### [>> GO TO SCENARIO #2](../scenario2/README.md) 141 | -------------------------------------------------------------------------------- /scenario1/docs/panfw_commit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario1/docs/panfw_commit.png -------------------------------------------------------------------------------- /scenario1/docs/panfw_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario1/docs/panfw_login.png -------------------------------------------------------------------------------- /scenario1/docs/panfw_monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario1/docs/panfw_monitor.png -------------------------------------------------------------------------------- /scenario1/docs/scenario1-allowmgmntnsgpubip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario1/docs/scenario1-allowmgmntnsgpubip.png -------------------------------------------------------------------------------- /scenario1/docs/scenario1-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario1/docs/scenario1-architecture.png -------------------------------------------------------------------------------- /scenario1/docs/spoke01-vm_check_internet_access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario1/docs/spoke01-vm_check_internet_access.png -------------------------------------------------------------------------------- /scenario1/docs/terraform_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario1/docs/terraform_apply.png -------------------------------------------------------------------------------- /scenario1/templates/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | .terraform.lock.hcl 8 | 9 | # Crash log files 10 | crash.log 11 | 12 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 13 | # .tfvars files are managed as part of configuration and so should be included in 14 | # version control. 15 | # 16 | # example.tfvars 17 | 18 | # Ignore override files as they are usually used to override resources locally and so 19 | # are not checked in 20 | override.tf 21 | override.tf.json 22 | *_override.tf 23 | *_override.tf.json 24 | 25 | # Include override files you do wish to add to version control using negated pattern 26 | # 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* -------------------------------------------------------------------------------- /scenario1/templates/files/bootstrap.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | * 7 | 8 | 9 | yes 10 | 11 | 12 | 13 | 14 | $5$oftmcqpp$LjvO/RDyvFtv.42RVo8TXNoP9Rj9toF.UF7RtuK.yV7 15 | 16 | 17 | yes 18 | 19 | 20 | 21 | 22 | 23 | yes 24 | 8 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | yes 37 | 5 38 | 39 | 40 | yes 41 | 5 42 | 43 | 44 | yes 45 | 5 46 | 47 | 48 | yes 49 | 10 50 | 51 | 52 | yes 53 | 5 54 | 55 | 56 | 57 | yes 58 | 59 | 60 | 61 | 10 62 | 10 63 | 64 | 100 65 | 50 66 | 67 | 68 | 69 | 10 70 | 10 71 | 72 | 100 73 | 50 74 | 75 | 76 | 77 | 78 | 79 | 100 80 | yes 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | no 93 | 94 | 95 | 96 | no 97 | 98 | 99 | no 100 | 101 | 102 | 103 | 104 | 105 | no 106 | 107 | 108 | 109 | 110 | 111 | 112 | no 113 | 114 | 115 | 116 | no 117 | 118 | 119 | no 120 | 121 | 122 | 123 | 124 | 125 | no 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 3 135 | 5 136 | wait-recover 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | aes-128-cbc 146 | 3des 147 | 148 | 149 | sha1 150 | 151 | 152 | group2 153 | 154 | 155 | 8 156 | 157 | 158 | 159 | 160 | aes-128-cbc 161 | 162 | 163 | sha256 164 | 165 | 166 | group19 167 | 168 | 169 | 8 170 | 171 | 172 | 173 | 174 | aes-256-cbc 175 | 176 | 177 | sha384 178 | 179 | 180 | group20 181 | 182 | 183 | 8 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | aes-128-cbc 192 | 3des 193 | 194 | 195 | sha1 196 | 197 | 198 | group2 199 | 200 | 1 201 | 202 | 203 | 204 | 205 | 206 | aes-128-gcm 207 | 208 | 209 | none 210 | 211 | 212 | group19 213 | 214 | 1 215 | 216 | 217 | 218 | 219 | 220 | aes-256-gcm 221 | 222 | 223 | none 224 | 225 | 226 | group20 227 | 228 | 1 229 | 230 | 231 | 232 | 233 | 234 | 235 | aes-128-cbc 236 | 237 | 238 | sha1 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | real-time 252 | 253 | 254 | high 255 | 256 | 257 | high 258 | 259 | 260 | medium 261 | 262 | 263 | medium 264 | 265 | 266 | low 267 | 268 | 269 | low 270 | 271 | 272 | low 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | no 285 | 286 | 287 | 1.25 288 | 0.5 289 | 900 290 | 300 291 | 900 292 | yes 293 | 294 | 295 | 296 | 297 | yes 298 | 299 | 300 | 301 | 302 | no 303 | 304 | 305 | no 306 | 307 | 308 | no 309 | 310 | 311 | 312 | ethernet1/1 313 | ethernet1/2 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | ${next-hop-trusted} 326 | 327 | 328 | None 329 | 330 | ethernet1/1 331 | 10 332 | 10.0.0.0/8 333 | 334 | 335 | 336 | 337 | 338 | 339 | ${next-hop-untrusted} 340 | 341 | 342 | None 343 | 344 | ethernet1/2 345 | 10 346 | 0.0.0.0/0 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | yes 362 | no 363 | no 364 | no 365 | 366 | 367 | updates.paloaltonetworks.com 368 | 369 | 370 | 371 | 372 | wednesday 373 | 01:02 374 | download-only 375 | 376 | 377 | 378 | 379 | US/Pacific 380 | 381 | yes 382 | yes 383 | 384 | panfw-vm 385 | 386 | 387 | 388 | yes 389 | 390 | 391 | FQDN 392 | 393 | panfw-vm 394 | panadmin 395 | 396 | 397 | yes 398 | no 399 | no 400 | no 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | ethernet1/1 416 | 417 | 418 | 419 | 420 | 421 | 422 | ethernet1/2 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | trust 436 | 437 | 438 | trust 439 | 440 | 441 | any 442 | 443 | 444 | any 445 | 446 | 447 | any 448 | 449 | 450 | any 451 | 452 | 453 | ping 454 | 455 | 456 | application-default 457 | 458 | 459 | any 460 | 461 | 462 | any 463 | 464 | allow 465 | yes 466 | 467 | 468 | 469 | untrust 470 | 471 | 472 | trust 473 | 474 | 475 | any 476 | 477 | 478 | any 479 | 480 | 481 | any 482 | 483 | 484 | any 485 | 486 | 487 | any 488 | 489 | 490 | service-http 491 | service-https 492 | 493 | 494 | any 495 | 496 | 497 | any 498 | 499 | allow 500 | yes 501 | 502 | 503 | 504 | any 505 | 506 | 507 | any 508 | 509 | 510 | any 511 | 512 | 513 | any 514 | 515 | 516 | any 517 | 518 | 519 | any 520 | 521 | 522 | any 523 | 524 | 525 | application-default 526 | 527 | 528 | any 529 | 530 | 531 | any 532 | 533 | deny 534 | yes 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | ethernet1/2 545 | 546 | 547 | 548 | 549 | untrust 550 | 551 | 552 | trust 553 | 554 | 555 | any 556 | 557 | 558 | any 559 | 560 | any 561 | ethernet1/2 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | ethernet1/1 570 | ethernet1/2 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | -------------------------------------------------------------------------------- /scenario1/templates/files/init-cfg.txt: -------------------------------------------------------------------------------- 1 | hostname=pan-fw-single -------------------------------------------------------------------------------- /scenario1/templates/firewall.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_network_security_group" "fw-mgmt-nsg" { 2 | name = "${var.firewall_vm_name}-mgmt-nsg" 3 | location = azurerm_resource_group.resource_group.location 4 | resource_group_name = azurerm_resource_group.resource_group.name 5 | } 6 | 7 | # Allow inbound access to Management subnet. 8 | 9 | data "http" "ipinfo" { 10 | url = "https://ifconfig.me" 11 | } 12 | 13 | 14 | resource "azurerm_network_security_rule" "network_security_rule_mgmt" { 15 | name = "mgmt-allow-inbound" 16 | resource_group_name = azurerm_resource_group.resource_group.name 17 | network_security_group_name = azurerm_network_security_group.fw-mgmt-nsg.name 18 | access = "Allow" 19 | direction = "Inbound" 20 | priority = 1000 21 | protocol = "Tcp" 22 | source_port_range = "*" 23 | source_address_prefixes = [coalesce(var.allow_inbound_mgmt_ips, data.http.ipinfo.response_body)] 24 | destination_address_prefix = "*" 25 | destination_port_range = "443" 26 | } 27 | 28 | resource "azurerm_subnet_network_security_group_association" "network_security_group_association_mgmt" { 29 | subnet_id = azurerm_subnet.subnet_pan_mgmt.id 30 | network_security_group_id = azurerm_network_security_group.fw-mgmt-nsg.id 31 | } 32 | 33 | resource "azurerm_network_security_group" "fw-untrusted-nsg" { 34 | name = "${var.firewall_vm_name}-untrusted-nsg" 35 | location = azurerm_resource_group.resource_group.location 36 | resource_group_name = azurerm_resource_group.resource_group.name 37 | } 38 | 39 | resource "azurerm_network_security_rule" "network_security_rule_untrusted" { 40 | name = "untrusted-allow-inbound" 41 | resource_group_name = azurerm_resource_group.resource_group.name 42 | network_security_group_name = azurerm_network_security_group.fw-untrusted-nsg.name 43 | access = "Allow" 44 | direction = "Inbound" 45 | priority = 1000 46 | protocol = "*" 47 | source_port_range = "*" 48 | source_address_prefix = "*" 49 | destination_address_prefix = module.paloalto_vmseries_01.interfaces["${var.firewall_vm_name}-untrusted"].private_ip_address 50 | destination_port_range = "*" 51 | } 52 | 53 | resource "azurerm_subnet_network_security_group_association" "network_security_group_association_untrusted" { 54 | subnet_id = azurerm_subnet.subnet_pan_untrusted.id 55 | network_security_group_id = azurerm_network_security_group.fw-untrusted-nsg.id 56 | } 57 | 58 | resource "random_integer" "id" { 59 | min = 100 60 | max = 999 61 | } 62 | 63 | resource "random_password" "password" { 64 | length = 16 65 | min_lower = 1 66 | min_numeric = 1 67 | min_special = 1 68 | min_upper = 1 69 | } 70 | 71 | resource "local_file" "bootstrap" { 72 | 73 | content = templatefile("${path.module}/files/bootstrap.tpl", { 74 | interface-trust = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 4)}/${split("/", azurerm_subnet.subnet_pan_trusted.address_prefixes[0])[1]}" 75 | interface-untrust = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 4)}/${split("/", azurerm_subnet.subnet_pan_untrusted.address_prefixes[0])[1]}" 76 | next-hop-trusted = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 1)}" 77 | next-hop-untrusted = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 1)}" 78 | }) 79 | filename = "${path.module}/files/bootstrap.xml" 80 | 81 | } 82 | 83 | module "bootstrap" { 84 | source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/bootstrap" 85 | version = "1.2.0" 86 | 87 | name = "paloaltobootstrap${random_integer.id.result}" 88 | location = azurerm_resource_group.resource_group.location 89 | resource_group_name = azurerm_resource_group.resource_group.name 90 | storage_share_name = "sharepaloaltobootstrap${random_integer.id.result}" 91 | storage_acl = false 92 | 93 | files = { 94 | "files/init-cfg.txt" = "config/init-cfg.txt" 95 | "files/bootstrap.xml" = "config/bootstrap.xml" 96 | } 97 | files_md5 = { 98 | "files/init-cfg.txt" = md5(file("files/init-cfg.txt")) 99 | "files/bootstrap.xml" = md5(local_file.bootstrap.content) 100 | } 101 | 102 | depends_on = [local_file.bootstrap] 103 | } 104 | 105 | resource "azurerm_public_ip" "fw_mgmt_pip" { 106 | name = "${var.firewall_vm_name}-mgmt-pip" 107 | location = azurerm_resource_group.resource_group.location 108 | resource_group_name = azurerm_resource_group.resource_group.name 109 | allocation_method = "Static" 110 | sku = "Standard" 111 | domain_name_label = "${var.firewall_vm_name}-mgmt-${random_integer.id.result}" 112 | } 113 | 114 | resource "azurerm_public_ip" "fw_untrusted_pip" { 115 | name = "${var.firewall_vm_name}-untrusted-pip" 116 | location = azurerm_resource_group.resource_group.location 117 | resource_group_name = azurerm_resource_group.resource_group.name 118 | allocation_method = "Static" 119 | sku = "Standard" 120 | } 121 | 122 | module "paloalto_vmseries_01" { 123 | source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/vmseries" 124 | version = "1.2.0" 125 | 126 | location = azurerm_resource_group.resource_group.location 127 | resource_group_name = azurerm_resource_group.resource_group.name 128 | name = var.firewall_vm_name 129 | username = var.username 130 | password = coalesce(var.password, random_password.password.result) 131 | img_version = var.common_vmseries_version 132 | img_sku = var.common_vmseries_sku 133 | vm_size = var.common_vmseries_vm_size 134 | enable_zones = var.enable_zones 135 | bootstrap_options = (join(",", 136 | [ 137 | "storage-account=${module.bootstrap.storage_account.name}", 138 | "access-key=${module.bootstrap.storage_account.primary_access_key}", 139 | "file-share=${module.bootstrap.storage_share.name}", 140 | "share-directory=None" 141 | ] 142 | )) 143 | interfaces = [ 144 | { 145 | name = "${var.firewall_vm_name}-mgmt" 146 | subnet_id = azurerm_subnet.subnet_pan_mgmt.id 147 | public_ip_name = azurerm_public_ip.fw_mgmt_pip.name 148 | public_ip_resource_group = azurerm_public_ip.fw_mgmt_pip.resource_group_name 149 | }, 150 | { 151 | name = "${var.firewall_vm_name}-trusted" 152 | subnet_id = azurerm_subnet.subnet_pan_trusted.id 153 | }, 154 | { 155 | name = "${var.firewall_vm_name}-untrusted" 156 | subnet_id = azurerm_subnet.subnet_pan_untrusted.id 157 | public_ip_name = azurerm_public_ip.fw_untrusted_pip.name 158 | public_ip_resource_group = azurerm_public_ip.fw_untrusted_pip.resource_group_name 159 | } 160 | ] 161 | depends_on = [module.bootstrap] 162 | } 163 | -------------------------------------------------------------------------------- /scenario1/templates/main.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_marketplace_agreement" "paloaltonetworks" { 2 | publisher = "paloaltonetworks" 3 | offer = "vmseries-flex" 4 | plan = "byol" 5 | } 6 | 7 | resource "azurerm_marketplace_agreement" "paloaltonetworks" { 8 | count = data.azurerm_marketplace_agreement.paloaltonetworks.id == null ? 1 : 0 9 | publisher = "paloaltonetworks" 10 | offer = "vmseries-flex" 11 | plan = "byol" 12 | } 13 | 14 | resource "azurerm_resource_group" "resource_group" { 15 | name = var.resource_group_name 16 | location = var.location 17 | } -------------------------------------------------------------------------------- /scenario1/templates/network.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_virtual_network" "virtual_network_hub" { 2 | name = var.vnet_hub_name 3 | address_space = [var.vnet_hub_address_space] 4 | location = azurerm_resource_group.resource_group.location 5 | resource_group_name = azurerm_resource_group.resource_group.name 6 | } 7 | 8 | resource "azurerm_subnet" "subnet_pan_mgmt" { 9 | name = "pan-mgmt-subnet" 10 | resource_group_name = azurerm_resource_group.resource_group.name 11 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 12 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 0)] 13 | } 14 | 15 | resource "azurerm_subnet" "subnet_pan_untrusted" { 16 | name = "pan-untrusted-subnet" 17 | resource_group_name = azurerm_resource_group.resource_group.name 18 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 19 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 1)] 20 | } 21 | 22 | resource "azurerm_subnet" "subnet_pan_trusted" { 23 | name = "pan-trusted-subnet" 24 | resource_group_name = azurerm_resource_group.resource_group.name 25 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 26 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 2)] 27 | } 28 | 29 | 30 | resource "azurerm_virtual_network" "virtual_network_spoke01" { 31 | name = var.vnet_spoke01_name 32 | address_space = [var.vnet_spoke01_address_space] 33 | location = azurerm_resource_group.resource_group.location 34 | resource_group_name = azurerm_resource_group.resource_group.name 35 | } 36 | 37 | resource "azurerm_subnet" "subnet_spoke01_default" { 38 | name = "snet-default" 39 | resource_group_name = azurerm_resource_group.resource_group.name 40 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke01.name 41 | address_prefixes = [cidrsubnet(var.vnet_spoke01_address_space, 4, 0)] 42 | } 43 | 44 | resource "azurerm_virtual_network_peering" "peering_hub_to_spoke01" { 45 | name = "hub-to-spoke01" 46 | resource_group_name = azurerm_resource_group.resource_group.name 47 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 48 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_spoke01.id 49 | allow_virtual_network_access = true 50 | allow_forwarded_traffic = true 51 | } 52 | 53 | resource "azurerm_virtual_network_peering" "peering_spoke01_to_hub" { 54 | name = "spoke01-to-hub" 55 | resource_group_name = azurerm_resource_group.resource_group.name 56 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke01.name 57 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_hub.id 58 | allow_virtual_network_access = true 59 | allow_forwarded_traffic = true 60 | } 61 | 62 | resource "azurerm_virtual_network" "virtual_network_spoke02" { 63 | name = var.vnet_spoke02_name 64 | address_space = [var.vnet_spoke02_address_space] 65 | location = azurerm_resource_group.resource_group.location 66 | resource_group_name = azurerm_resource_group.resource_group.name 67 | } 68 | 69 | resource "azurerm_subnet" "subnet_spoke02_default" { 70 | name = "snet-default" 71 | resource_group_name = azurerm_resource_group.resource_group.name 72 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke02.name 73 | address_prefixes = [cidrsubnet(var.vnet_spoke02_address_space, 4, 0)] 74 | } 75 | 76 | resource "azurerm_virtual_network_peering" "peering_hub_to_spoke02" { 77 | name = "hub-to-spoke02" 78 | resource_group_name = azurerm_resource_group.resource_group.name 79 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 80 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_spoke02.id 81 | allow_virtual_network_access = true 82 | allow_forwarded_traffic = true 83 | } 84 | 85 | resource "azurerm_virtual_network_peering" "peering_spoke02_to_hub" { 86 | name = "spoke02-to-hub" 87 | resource_group_name = azurerm_resource_group.resource_group.name 88 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke02.name 89 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_hub.id 90 | allow_virtual_network_access = true 91 | allow_forwarded_traffic = true 92 | } 93 | 94 | resource "azurerm_route_table" "route_table_spoke01" { 95 | name = "spoke01-rt" 96 | location = azurerm_resource_group.resource_group.location 97 | resource_group_name = azurerm_resource_group.resource_group.name 98 | disable_bgp_route_propagation = true 99 | } 100 | 101 | resource "azurerm_route" "route_spoke01_default" { 102 | name = "spoke01-default-route" 103 | resource_group_name = azurerm_resource_group.resource_group.name 104 | route_table_name = azurerm_route_table.route_table_spoke01.name 105 | address_prefix = "0.0.0.0/0" 106 | next_hop_type = "VirtualAppliance" 107 | next_hop_in_ip_address = module.paloalto_vmseries_01.interfaces["${var.firewall_vm_name}-trusted"].private_ip_address 108 | } 109 | 110 | resource "azurerm_subnet_route_table_association" "route_table_association_spoke01" { 111 | subnet_id = azurerm_subnet.subnet_spoke01_default.id 112 | route_table_id = azurerm_route_table.route_table_spoke01.id 113 | } 114 | 115 | 116 | resource "azurerm_route_table" "route_table_spoke02" { 117 | name = "spoke02-rt" 118 | location = azurerm_resource_group.resource_group.location 119 | resource_group_name = azurerm_resource_group.resource_group.name 120 | disable_bgp_route_propagation = true 121 | } 122 | 123 | resource "azurerm_route" "route_spoke02_default" { 124 | name = "spoke02-default-route" 125 | resource_group_name = azurerm_resource_group.resource_group.name 126 | route_table_name = azurerm_route_table.route_table_spoke02.name 127 | address_prefix = "0.0.0.0/0" 128 | next_hop_type = "VirtualAppliance" 129 | next_hop_in_ip_address = module.paloalto_vmseries_01.interfaces["${var.firewall_vm_name}-trusted"].private_ip_address 130 | } 131 | 132 | resource "azurerm_subnet_route_table_association" "route_table_association_spoke02" { 133 | subnet_id = azurerm_subnet.subnet_spoke02_default.id 134 | route_table_id = azurerm_route_table.route_table_spoke02.id 135 | } -------------------------------------------------------------------------------- /scenario1/templates/output.tf: -------------------------------------------------------------------------------- 1 | output "paloalto_vmseries_01_dns" { 2 | value = "https://${azurerm_public_ip.fw_mgmt_pip.fqdn}" 3 | } 4 | 5 | output "paloalto_username" { 6 | value = var.username 7 | } 8 | 9 | # Use "terraform output paloalto_password" to get the password after terraform apply 10 | output "paloalto_password" { 11 | value = coalesce(var.password, random_password.password.result) 12 | sensitive = true 13 | } -------------------------------------------------------------------------------- /scenario1/templates/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = "=3.76" 6 | } 7 | 8 | http = { 9 | source = "hashicorp/http" 10 | version = "3.1.0" 11 | } 12 | } 13 | } 14 | 15 | provider "azurerm" { 16 | features { 17 | resource_group { 18 | prevent_deletion_if_contains_resources = false 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /scenario1/templates/variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | type = string 3 | default = "West Europe" 4 | } 5 | 6 | variable "resource_group_name" { 7 | type = string 8 | default = "rg-panfw-scenario1" 9 | } 10 | 11 | variable "vnet_hub_name" { 12 | type = string 13 | default = "hub-vnet" 14 | } 15 | 16 | variable "vnet_hub_address_space" { 17 | type = string 18 | default = "10.0.0.0/24" 19 | } 20 | 21 | variable "vnet_spoke01_name" { 22 | type = string 23 | default = "spoke01-vnet" 24 | } 25 | 26 | variable "vnet_spoke01_address_space" { 27 | type = string 28 | default = "10.0.1.0/24" 29 | } 30 | 31 | variable "vnet_spoke02_name" { 32 | type = string 33 | default = "spoke02-vnet" 34 | } 35 | 36 | variable "vnet_spoke02_address_space" { 37 | type = string 38 | default = "10.0.2.0/24" 39 | } 40 | 41 | variable "firewall_vm_name" { 42 | type = string 43 | default = "panfw-vm" 44 | } 45 | 46 | variable "allow_inbound_mgmt_ips" { 47 | default = "" 48 | type = string 49 | } 50 | 51 | variable "common_vmseries_sku" { 52 | description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" 53 | default = "byol" 54 | type = string 55 | } 56 | 57 | variable "common_vmseries_version" { 58 | description = "VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`" 59 | default = "latest" 60 | type = string 61 | } 62 | 63 | variable "common_vmseries_vm_size" { 64 | description = "Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported." 65 | default = "Standard_D3_v2" 66 | type = string 67 | } 68 | 69 | variable "username" { 70 | description = "Initial administrative username to use for all systems." 71 | default = "panadmin" 72 | type = string 73 | } 74 | 75 | variable "password" { 76 | description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." 77 | default = "Microsoft=1Microsoft=1" 78 | type = string 79 | } 80 | 81 | variable "avzones" { 82 | type = list(string) 83 | default = ["1", "2", "3"] 84 | } 85 | 86 | variable "enable_zones" { 87 | type = bool 88 | default = false 89 | } 90 | 91 | variable "vm_size" { 92 | type = string 93 | default = "Standard_DS1_v2" 94 | description = "VM Size" 95 | } 96 | 97 | variable "vm_os_publisher" { 98 | type = string 99 | default = "canonical" 100 | description = "VM OS Publisher" 101 | } 102 | 103 | variable "vm_os_offer" { 104 | type = string 105 | #default = "UbuntuServer" 106 | default = "0001-com-ubuntu-server-jammy" 107 | description = "VM OS Offer" 108 | } 109 | 110 | variable "vm_os_sku" { 111 | type = string 112 | default = "22_04-lts-gen2" 113 | description = "VM OS Sku" 114 | } 115 | 116 | variable "vm_os_version" { 117 | type = string 118 | default = "latest" 119 | description = "VM OS Version" 120 | } 121 | 122 | locals { 123 | custom_script_spoke01 = < **Note**: The Firewall may take between 5-10 minutes to start up. If the console does not appear, feel free to refresh the page. 76 | 77 | ## Task 4: Configure HA interfaces 78 | 79 | > Below steps must be **executed on both Palo Alto Consoles** of the two appliances: 80 | 81 | * Navigate to the *Network* tab in the administration console. Under *Interfaces*, select *ethernet1/3* interface 82 | * Configure *ethernet1/3* as `HA` interface type: 83 | 84 | ![img](docs/scenario2-configure-ethernet13.png) 85 | 86 | * Click 'OK' and Commit changes 87 | * Check *ethernet1/3* is configure and up: 88 | 89 | ![img](docs/scenario2-configure-ethernet13-configured-and-up.png) 90 | 91 | ## Task 5: Configure High Availability 92 | 93 | To configure High Availability, it is required to configure HA1 and HA2 links: 94 | * The HA1 link (Control Link), is primarily used for control information exchange and synchronization between HA peers (heartbeats, hello message and configuration synchronization data). It is a best practice to use management interface as HA1. 95 | * The HA2 link (Data Link) is responsible for carrying session setup information and data forwarding between the active and passive firewalls in HA pair. This ensures that the passive appliance has the same session information as the active appliance. 96 | 97 | > Below steps must be executed on `panfw-vm-01` Palo Alto Console. 98 | 99 | * Navigate to the 'Device' tab in the administration console and select 'High Availability'. 100 | * Setup HA pair: 101 | * Enable HA: checked 102 | * Group ID: 1 103 | * Peer HA1 IP Address: 10.0.0.5 (*management private IP address*) 104 | 105 | 106 | ![img](docs/scenario2-configure-panfw01-ha-general.png) 107 | 108 | * Click 'OK' and Commit changes 109 | * Go to 'HA Communications' tab and configure Data links HA2: 110 | * Enable Session Synchronization: checked 111 | * Port: ethernet1/3 112 | * IPv4/IPv6 Address: 10.0.0.52 113 | * Netmask: 255.255.255.240 114 | 115 | ![img](docs/scenario2-configure-panfw01-ha-hacommunications.png) 116 | 117 | * Click 'OK' and Commit changes 118 | 119 | > Below steps must be executed on `panfw-vm-02` Palo Alto Console. 120 | 121 | * Navigate to the 'Device' tab in the administration console and select 'High Availability' 122 | * Setup HA pair: 123 | * Enable HA: checked 124 | * Group ID: 1 125 | * Peer HA1 IP Address: 10.0.0.4 (*management private IP address*) 126 | * Click 'OK' and Commit changes 127 | * Go to 'HA Communications' tab and configure Data links HA2: 128 | * Enable Session Synchronization: checked 129 | * Port: ethernet1/3 130 | * IPv4/IPv6 Address: 10.0.0.53 131 | * Netmask: 255.255.255.240 132 | 133 | * Click 'OK' and Commit changes 134 | 135 | ## Task 6: Display High Availability Status 136 | 137 | > Below steps must be **executed on both Palo Alto Consoles** of the two appliances: 138 | 139 | * Navigate to the 'Dashboard' tab in the administration console. Add "Interfaces", "High Availability" and "HA Cluster" Widgets: 140 | 141 | ![img](docs/scenario2-dashboard.png) 142 | 143 | * Observe the HA status on both appliances: 144 | 145 | ![img](docs/scenario2-dashboard-status.png) 146 | 147 | It is noticeable that: 148 | 1) There is one appliance in an active state and another in a passive state. 149 | 2) The interfaces of the passive appliance are down, which is standard operation in an HA pair. In this setup, the active appliance handles all traffic, while the other appliance remains passive and takes over only if the active appliance fails. Therefore, the passive appliance interfaces remain down until a failover event occurs. 150 | 151 | 152 | * Click on 'Sync to peer' link on the Active instance. 153 | 154 | ## Task 7: Check - Policies Synchronisation 155 | 156 | > Below tasks must be execute on one of the two instances. The goal is to verify that after the commit, the created security rule will be replicated on the other instance. 157 | 158 | * Navigate to the *Policies* tab in the administration console. Under *Policies*, select *Security*. You will find a list of all security rules 159 | 160 | * Create a new security rule by clicking on 'Add'. Configure the rule as follows: 161 | 162 | * Name: DUMB-RULE 163 | * Description: Dumb Rule to check synchronisation 164 | * Source zone: `trust` 165 | * Destination zone: `untrust` 166 | * Application: `windows-azure-service-updates` 167 | * Action: Allow 168 | * Log at Session Start: `checked` 169 | * Log at Session End: `checked` 170 | 171 | Click on 'OK' to save the rule 172 | 173 | * To apply new created rule, commit your changes to update the firewall's configuration 174 | 175 | * After the commit has been made, check on the second instance that the new rule properly appeared 176 | 177 | ## Task 8: Configure fallback IP address 178 | 179 | > Below steps must be executed on Active instance only. In this case, it is `panfw-vm-01`. 180 | * In the Azure Portal, navigate to the trusted NIC and add a new IP configuration with the following details: 181 | * Name: trustvip 182 | * Allocation: Static 183 | * Private IP Address: 10.0.0.38 184 | 185 | ![img](docs/scenario2-add-trustvip.png) 186 | 187 | * Navigate to the untrusted NIC and modify existing primary IP configuration to associate public IP address `panfw-vm-untrusted-pip` which was created during terraform deployment: 188 | 189 | ![img](docs/scenario2-add-untrustvip.png) 190 | 191 | ## Task 9: Palo Alto: Configure HA Service principal 192 | 193 | A Service Principal is required to automate the failover process. 194 | 195 | > Service Principal is an identity created to use with applications, hosted services, and automated tools to access Azure resources securely. This access is restricted by the roles assigned to the service principal, giving you control over which resources can be accessed and at what level. 196 | 197 | For automatic failover, the Service Principal is needed to update network configurations like managing IP addresses during a failover event (moving the failover IP from previous Active appliance to Passive appliance). 198 | 199 | * Create a Service Principal: 200 | 201 | ```bash 202 | $ az ad sp create-for-rbac --name "AZ_SP_PANFW" --skip-assignment 203 | ``` 204 | ![img](docs/scenario2-create-sp.png) 205 | 206 | * In the Azure Portal, grand the `Contributor` built-in role to the Service Principal on the `rg-panfw-scenario2` resource group: 207 | 208 | ```bash 209 | $ az role assignment create --assignee --role Contributor --scope "/subscriptions/SUBSCRIPTION_ID/resourceGroups/rg-panfw-scenario2" 210 | ``` 211 | 212 | Notes: 213 | 1) Replace SUBSCRIPTION_ID with your subscription id in 214 | 2) It is possible to apply more granual permissions as explained in [the documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/configure-activepassive-ha-for-vm-series-firewall-on-azure). Contributor built-role will be enough for this MicroHack. 215 | 216 | > Below steps must be **executed on both Palo Alto Console** of the two appliances. 217 | 218 | * Navigate to the 'Device' tab in the administration console and select 'VM-Series' 219 | * Configure Azure HA Configuration: 220 | * Client ID: `appId` value 221 | * Client Secret: `password` value 222 | * Tenant ID: `tenant` value 223 | * Subscription ID: Execute `$ az account show --query id --output tsv` to get the `subscriptionId` 224 | * Resource Group: `rg-panfw-scenario2` 225 | * Commit your changes 226 | 227 | ![img](docs/scenario2-configure-ha-sp.png) 228 | 229 | ## Task 10: Check - Active/Passive failover 230 | 231 | * In the Azure Portal, navigate to the Active appliance a shutdown the virtual machine 232 | * Check that the appliance which was Passive becomes Active: 233 | 234 | ![](docs/scenario2-passive-appliance-becomes-active.png) 235 | 236 | After approximately 2 minutes, it can be observed that the appliance, which is now active, starts receiving traffic. 237 | 238 | This is because the `trustedvip` has been shifted to the NIC of the appliance, which was previously passive and is now active. Similarly, the public IP `panfw-vm-untrusted-pip` is now associated with the untrusted NIC of the appliance, which transitioned from being passive to active. 239 | 240 | 241 | ## 🏁 Results 242 | 243 | * We deployed two instances of the VM-Series firewall on Azure in an Active-Passive HA configuration. The Active instance utilized a floating trusted IP and a floating untrusted public IP. 244 | * The HA interfaces were configured for data synchronization and health monitoring. High Availability was set up between the instances ensuring seamless failover during any instance failure. 245 | * The security rule synchronization between the two instances was successfully verified. A fallback IP address was also configured. 246 | * A Service Principal was created and assigned a 'Contributor' role. This automated the failover process and managed IP addresses during a failover event. 247 | * The failover process was tested by manually shutting down the active appliance from the Azure Portal. The previously passive appliance transitioned to an active state. 248 | * After approximately 2 minutes, the now active appliance started receiving traffic. This demonstrated the effectiveness of the Active/Passive configuration in maintaining high availability and ensuring seamless failover. 249 | 250 | # Appendix 251 | 252 | * [Azure Terraform VMSeries Fast HA Failover](https://github.com/PaloAltoNetworks/azure-terraform-vmseries-fast-ha-failover) 253 | 254 | ### [>> GO TO SCENARIO #3](../scenario3/README.md) 255 | -------------------------------------------------------------------------------- /scenario2/docs/scenario2-add-trustvip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-add-trustvip.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-add-untrustvip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-add-untrustvip.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-allowmgmntnsgpubip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-allowmgmntnsgpubip.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-architecture-failover.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-architecture-failover.gif -------------------------------------------------------------------------------- /scenario2/docs/scenario2-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-architecture.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-configure-ethernet13-configured-and-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-configure-ethernet13-configured-and-up.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-configure-ethernet13-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-configure-ethernet13-green.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-configure-ethernet13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-configure-ethernet13.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-configure-ha-sp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-configure-ha-sp.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-configure-panfw01-ha-general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-configure-panfw01-ha-general.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-configure-panfw01-ha-hacommunications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-configure-panfw01-ha-hacommunications.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-create-sp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-create-sp.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-dashboard-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-dashboard-status.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-dashboard.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-device-ha-general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-device-ha-general.png -------------------------------------------------------------------------------- /scenario2/docs/scenario2-passive-appliance-becomes-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/scenario2-passive-appliance-becomes-active.png -------------------------------------------------------------------------------- /scenario2/docs/terraform_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario2/docs/terraform_apply.png -------------------------------------------------------------------------------- /scenario2/templates/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | .terraform.lock.hcl 8 | 9 | # Crash log files 10 | crash.log 11 | 12 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 13 | # .tfvars files are managed as part of configuration and so should be included in 14 | # version control. 15 | # 16 | # example.tfvars 17 | 18 | # Ignore override files as they are usually used to override resources locally and so 19 | # are not checked in 20 | override.tf 21 | override.tf.json 22 | *_override.tf 23 | *_override.tf.json 24 | 25 | # Include override files you do wish to add to version control using negated pattern 26 | # 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* -------------------------------------------------------------------------------- /scenario2/templates/files/init-cfg.txt: -------------------------------------------------------------------------------- 1 | hostname=pan-fw-ha-active-passive -------------------------------------------------------------------------------- /scenario2/templates/firewall.tf: -------------------------------------------------------------------------------- 1 | resource "random_integer" "id" { 2 | min = 100 3 | max = 999 4 | } 5 | 6 | resource "random_integer" "id2" { 7 | min = 100 8 | max = 999 9 | } 10 | 11 | resource "random_password" "password" { 12 | length = 16 13 | min_lower = 1 14 | min_numeric = 1 15 | min_special = 1 16 | min_upper = 1 17 | } 18 | 19 | # Allow inbound access to Management subnet. 20 | 21 | data "http" "ipinfo" { 22 | url = "https://ifconfig.me" 23 | } 24 | 25 | resource "azurerm_public_ip" "fws_untrusted_pip" { 26 | name = "${var.firewall_vm_name}-untrusted-pip" 27 | location = azurerm_resource_group.resource_group.location 28 | resource_group_name = azurerm_resource_group.resource_group.name 29 | allocation_method = "Static" 30 | sku = "Standard" 31 | } 32 | 33 | # Firewall #1 34 | 35 | resource "azurerm_public_ip" "fw01_mgmt_pip" { 36 | name = "${var.firewall_vm_name}-01-mgmt-pip" 37 | location = azurerm_resource_group.resource_group.location 38 | resource_group_name = azurerm_resource_group.resource_group.name 39 | allocation_method = "Static" 40 | sku = "Standard" 41 | domain_name_label = "${var.firewall_vm_name}-mgmt-01-${random_integer.id.result}" 42 | } 43 | 44 | resource "local_file" "bootstrap01" { 45 | 46 | content = templatefile("${path.module}/files/bootstrap.tpl", { 47 | interface-trust-01 = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 4)}/32" 48 | interface-trust-02 = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 5)}/32" 49 | interface-trust-failover = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 6)}/${split("/", azurerm_subnet.subnet_pan_trusted.address_prefixes[0])[1]}" 50 | interface-untrust-01 = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 4)}/32" 51 | interface-untrust-02 = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 5)}/32" 52 | next-hop-trusted = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 1)}" 53 | next-hop-untrusted = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 1)}" 54 | }) 55 | filename = "${path.module}/files/bootstrap01.xml" 56 | 57 | } 58 | 59 | module "bootstrap01" { 60 | source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/bootstrap" 61 | version = "1.2.0" 62 | 63 | name = "paloaltobootstrap${random_integer.id.result}" 64 | location = azurerm_resource_group.resource_group.location 65 | resource_group_name = azurerm_resource_group.resource_group.name 66 | storage_share_name = "sharepaloaltobootstrap${random_integer.id.result}" 67 | storage_acl = false 68 | 69 | files = { 70 | "files/init-cfg.txt" = "config/init-cfg.txt" 71 | "files/bootstrap01.xml" = "config/bootstrap.xml" 72 | } 73 | files_md5 = { 74 | "files/init-cfg.txt" = md5(file("files/init-cfg.txt")) 75 | "files/bootstrap01.xml" = md5(local_file.bootstrap01.content) 76 | } 77 | 78 | depends_on = [local_file.bootstrap01] 79 | } 80 | 81 | module "paloalto_vmseries_01" { 82 | source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/vmseries" 83 | version = "1.2.0" 84 | 85 | location = azurerm_resource_group.resource_group.location 86 | resource_group_name = azurerm_resource_group.resource_group.name 87 | name = "${var.firewall_vm_name}-01" 88 | username = var.username 89 | password = coalesce(var.password, random_password.password.result) 90 | img_version = var.common_vmseries_version 91 | img_sku = var.common_vmseries_sku 92 | vm_size = var.common_vmseries_vm_size 93 | enable_zones = var.enable_zones 94 | bootstrap_options = (join(",", 95 | [ 96 | "storage-account=${module.bootstrap01.storage_account.name}", 97 | "access-key=${module.bootstrap01.storage_account.primary_access_key}", 98 | "file-share=${module.bootstrap01.storage_share.name}", 99 | "share-directory=None" 100 | ] 101 | )) 102 | interfaces = [ 103 | { 104 | name = "${var.firewall_vm_name}-01-mgmt" 105 | subnet_id = azurerm_subnet.subnet_pan_mgmt.id 106 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_mgmt.address_prefixes[0], 4) 107 | public_ip_name = azurerm_public_ip.fw01_mgmt_pip.name 108 | public_ip_resource_group = azurerm_public_ip.fw01_mgmt_pip.resource_group_name 109 | }, 110 | { 111 | name = "${var.firewall_vm_name}-01-trusted" 112 | subnet_id = azurerm_subnet.subnet_pan_trusted.id 113 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 4) 114 | }, 115 | { 116 | name = "${var.firewall_vm_name}-01-untrusted" 117 | subnet_id = azurerm_subnet.subnet_pan_untrusted.id 118 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 4) 119 | }, 120 | { 121 | name = "${var.firewall_vm_name}-01-ha" 122 | subnet_id = azurerm_subnet.subnet_pan_ha.id 123 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_ha.address_prefixes[0], 4) 124 | } 125 | ] 126 | depends_on = [module.bootstrap01] 127 | } 128 | 129 | # Firewall #2 130 | 131 | resource "azurerm_public_ip" "fw02_mgmt_pip" { 132 | name = "${var.firewall_vm_name}-02-mgmt-pip" 133 | location = azurerm_resource_group.resource_group.location 134 | resource_group_name = azurerm_resource_group.resource_group.name 135 | allocation_method = "Static" 136 | sku = "Standard" 137 | domain_name_label = "${var.firewall_vm_name}-mgmt-02-${random_integer.id.result}" 138 | } 139 | 140 | resource "local_file" "bootstrap02" { 141 | 142 | content = templatefile("${path.module}/files/bootstrap.tpl", { 143 | interface-trust-01 = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 4)}/32" 144 | interface-trust-02 = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 5)}/32" 145 | interface-trust-failover = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 6)}/${split("/", azurerm_subnet.subnet_pan_trusted.address_prefixes[0])[1]}" 146 | interface-untrust-01 = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 4)}/32" 147 | interface-untrust-02 = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 5)}/32" 148 | next-hop-trusted = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 1)}" 149 | next-hop-untrusted = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 1)}" 150 | }) 151 | filename = "${path.module}/files/bootstrap02.xml" 152 | 153 | } 154 | 155 | module "bootstrap02" { 156 | source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/bootstrap" 157 | version = "1.2.0" 158 | 159 | name = "paloaltobootstrap${random_integer.id2.result}" 160 | location = azurerm_resource_group.resource_group.location 161 | resource_group_name = azurerm_resource_group.resource_group.name 162 | storage_share_name = "sharepaloaltobootstrap${random_integer.id2.result}" 163 | storage_acl = false 164 | 165 | files = { 166 | "files/init-cfg.txt" = "config/init-cfg.txt" 167 | "files/bootstrap02.xml" = "config/bootstrap.xml" 168 | } 169 | files_md5 = { 170 | "files/init-cfg.txt" = md5(file("files/init-cfg.txt")) 171 | "files/bootstrap02.xml" = md5(local_file.bootstrap02.content) 172 | } 173 | 174 | depends_on = [local_file.bootstrap02] 175 | } 176 | 177 | module "paloalto_vmseries_02" { 178 | source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/vmseries" 179 | version = "1.2.0" 180 | 181 | location = azurerm_resource_group.resource_group.location 182 | resource_group_name = azurerm_resource_group.resource_group.name 183 | name = "${var.firewall_vm_name}-02" 184 | username = var.username 185 | password = coalesce(var.password, random_password.password.result) 186 | img_version = var.common_vmseries_version 187 | img_sku = var.common_vmseries_sku 188 | vm_size = var.common_vmseries_vm_size 189 | enable_zones = var.enable_zones 190 | bootstrap_options = (join(",", 191 | [ 192 | "storage-account=${module.bootstrap02.storage_account.name}", 193 | "access-key=${module.bootstrap02.storage_account.primary_access_key}", 194 | "file-share=${module.bootstrap02.storage_share.name}", 195 | "share-directory=None" 196 | ] 197 | )) 198 | interfaces = [ 199 | { 200 | name = "${var.firewall_vm_name}-02-mgmt" 201 | subnet_id = azurerm_subnet.subnet_pan_mgmt.id 202 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_mgmt.address_prefixes[0], 5) 203 | public_ip_name = azurerm_public_ip.fw02_mgmt_pip.name 204 | public_ip_resource_group = azurerm_public_ip.fw02_mgmt_pip.resource_group_name 205 | }, 206 | { 207 | name = "${var.firewall_vm_name}-02-trusted" 208 | subnet_id = azurerm_subnet.subnet_pan_trusted.id 209 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 5) 210 | }, 211 | { 212 | name = "${var.firewall_vm_name}-02-untrusted" 213 | subnet_id = azurerm_subnet.subnet_pan_untrusted.id 214 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 5) 215 | }, 216 | { 217 | name = "${var.firewall_vm_name}-02-ha" 218 | subnet_id = azurerm_subnet.subnet_pan_ha.id 219 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_ha.address_prefixes[0], 5) 220 | } 221 | ] 222 | depends_on = [module.bootstrap02] 223 | } 224 | 225 | # NSGs 226 | 227 | resource "azurerm_network_security_group" "fws-mgmt-nsg" { 228 | name = "${var.firewall_vm_name}-mgmt-nsg" 229 | location = azurerm_resource_group.resource_group.location 230 | resource_group_name = azurerm_resource_group.resource_group.name 231 | } 232 | 233 | resource "azurerm_network_security_rule" "network_security_rule_mgmt" { 234 | name = "mgmt-allow-inbound" 235 | resource_group_name = azurerm_resource_group.resource_group.name 236 | network_security_group_name = azurerm_network_security_group.fws-mgmt-nsg.name 237 | access = "Allow" 238 | direction = "Inbound" 239 | priority = 1000 240 | protocol = "Tcp" 241 | source_port_range = "*" 242 | source_address_prefixes = [coalesce(var.allow_inbound_mgmt_ips, data.http.ipinfo.response_body)] 243 | destination_address_prefix = "*" 244 | destination_port_range = "443" 245 | } 246 | 247 | resource "azurerm_subnet_network_security_group_association" "network_security_group_association_mgmt" { 248 | subnet_id = azurerm_subnet.subnet_pan_mgmt.id 249 | network_security_group_id = azurerm_network_security_group.fws-mgmt-nsg.id 250 | } 251 | 252 | resource "azurerm_network_security_group" "fws-untrusted-nsg" { 253 | name = "${var.firewall_vm_name}-untrusted-nsg" 254 | location = azurerm_resource_group.resource_group.location 255 | resource_group_name = azurerm_resource_group.resource_group.name 256 | } 257 | 258 | resource "azurerm_network_security_rule" "network_security_rule_untrusted" { 259 | name = "untrusted-allow-inbound" 260 | resource_group_name = azurerm_resource_group.resource_group.name 261 | network_security_group_name = azurerm_network_security_group.fws-untrusted-nsg.name 262 | access = "Allow" 263 | direction = "Inbound" 264 | priority = 1000 265 | protocol = "*" 266 | source_port_range = "*" 267 | source_address_prefix = "*" 268 | destination_address_prefix = module.paloalto_vmseries_01.interfaces["${var.firewall_vm_name}-01-untrusted"].private_ip_address 269 | destination_port_range = "*" 270 | } 271 | 272 | resource "azurerm_subnet_network_security_group_association" "network_security_group_association_untrusted" { 273 | subnet_id = azurerm_subnet.subnet_pan_untrusted.id 274 | network_security_group_id = azurerm_network_security_group.fws-untrusted-nsg.id 275 | } -------------------------------------------------------------------------------- /scenario2/templates/main.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_marketplace_agreement" "paloaltonetworks" { 2 | publisher = "paloaltonetworks" 3 | offer = "vmseries-flex" 4 | plan = "byol" 5 | } 6 | 7 | resource "azurerm_marketplace_agreement" "paloaltonetworks" { 8 | count = data.azurerm_marketplace_agreement.paloaltonetworks.id == null ? 1 : 0 9 | publisher = "paloaltonetworks" 10 | offer = "vmseries-flex" 11 | plan = "byol" 12 | } 13 | 14 | resource "azurerm_resource_group" "resource_group" { 15 | name = var.resource_group_name 16 | location = var.location 17 | } -------------------------------------------------------------------------------- /scenario2/templates/network.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_virtual_network" "virtual_network_hub" { 2 | name = var.vnet_hub_name 3 | address_space = [var.vnet_hub_address_space] 4 | location = azurerm_resource_group.resource_group.location 5 | resource_group_name = azurerm_resource_group.resource_group.name 6 | } 7 | 8 | resource "azurerm_subnet" "subnet_pan_mgmt" { 9 | name = "pan-mgmt-subnet" 10 | resource_group_name = azurerm_resource_group.resource_group.name 11 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 12 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 0)] 13 | } 14 | 15 | resource "azurerm_subnet" "subnet_pan_untrusted" { 16 | name = "pan-untrusted-subnet" 17 | resource_group_name = azurerm_resource_group.resource_group.name 18 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 19 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 1)] 20 | } 21 | 22 | resource "azurerm_subnet" "subnet_pan_trusted" { 23 | name = "pan-trusted-subnet" 24 | resource_group_name = azurerm_resource_group.resource_group.name 25 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 26 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 2)] 27 | } 28 | 29 | resource "azurerm_subnet" "subnet_pan_ha" { 30 | name = "pan-ha-subnet" 31 | resource_group_name = azurerm_resource_group.resource_group.name 32 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 33 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 3)] 34 | } 35 | 36 | 37 | resource "azurerm_virtual_network" "virtual_network_spoke01" { 38 | name = var.vnet_spoke01_name 39 | address_space = [var.vnet_spoke01_address_space] 40 | location = azurerm_resource_group.resource_group.location 41 | resource_group_name = azurerm_resource_group.resource_group.name 42 | } 43 | 44 | resource "azurerm_subnet" "subnet_spoke01_default" { 45 | name = "snet-default" 46 | resource_group_name = azurerm_resource_group.resource_group.name 47 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke01.name 48 | address_prefixes = [cidrsubnet(var.vnet_spoke01_address_space, 4, 0)] 49 | } 50 | 51 | resource "azurerm_virtual_network_peering" "peering_hub_to_spoke01" { 52 | name = "hub-to-spoke01" 53 | resource_group_name = azurerm_resource_group.resource_group.name 54 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 55 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_spoke01.id 56 | allow_virtual_network_access = true 57 | allow_forwarded_traffic = true 58 | } 59 | 60 | resource "azurerm_virtual_network_peering" "peering_spoke01_to_hub" { 61 | name = "spoke01-to-hub" 62 | resource_group_name = azurerm_resource_group.resource_group.name 63 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke01.name 64 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_hub.id 65 | allow_virtual_network_access = true 66 | allow_forwarded_traffic = true 67 | } 68 | 69 | resource "azurerm_virtual_network" "virtual_network_spoke02" { 70 | name = var.vnet_spoke02_name 71 | address_space = [var.vnet_spoke02_address_space] 72 | location = azurerm_resource_group.resource_group.location 73 | resource_group_name = azurerm_resource_group.resource_group.name 74 | } 75 | 76 | resource "azurerm_subnet" "subnet_spoke02_default" { 77 | name = "snet-default" 78 | resource_group_name = azurerm_resource_group.resource_group.name 79 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke02.name 80 | address_prefixes = [cidrsubnet(var.vnet_spoke02_address_space, 4, 0)] 81 | } 82 | 83 | resource "azurerm_virtual_network_peering" "peering_hub_to_spoke02" { 84 | name = "hub-to-spoke02" 85 | resource_group_name = azurerm_resource_group.resource_group.name 86 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 87 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_spoke02.id 88 | allow_virtual_network_access = true 89 | allow_forwarded_traffic = true 90 | } 91 | 92 | resource "azurerm_virtual_network_peering" "peering_spoke02_to_hub" { 93 | name = "spoke02-to-hub" 94 | resource_group_name = azurerm_resource_group.resource_group.name 95 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke02.name 96 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_hub.id 97 | allow_virtual_network_access = true 98 | allow_forwarded_traffic = true 99 | } 100 | 101 | resource "azurerm_route_table" "route_table_spoke01" { 102 | name = "spoke01-rt" 103 | location = azurerm_resource_group.resource_group.location 104 | resource_group_name = azurerm_resource_group.resource_group.name 105 | disable_bgp_route_propagation = true 106 | } 107 | 108 | resource "azurerm_route" "route_spoke01_default" { 109 | name = "spoke01-default-route" 110 | resource_group_name = azurerm_resource_group.resource_group.name 111 | route_table_name = azurerm_route_table.route_table_spoke01.name 112 | address_prefix = "0.0.0.0/0" 113 | next_hop_type = "VirtualAppliance" 114 | next_hop_in_ip_address = cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 6) //module.paloalto_vmseries_01.interfaces["${var.firewall_vm_name}-01-trusted"].private_ip_address 115 | } 116 | 117 | resource "azurerm_subnet_route_table_association" "route_table_association_spoke01" { 118 | subnet_id = azurerm_subnet.subnet_spoke01_default.id 119 | route_table_id = azurerm_route_table.route_table_spoke01.id 120 | } 121 | 122 | 123 | resource "azurerm_route_table" "route_table_spoke02" { 124 | name = "spoke02-rt" 125 | location = azurerm_resource_group.resource_group.location 126 | resource_group_name = azurerm_resource_group.resource_group.name 127 | disable_bgp_route_propagation = true 128 | } 129 | 130 | resource "azurerm_route" "route_spoke02_default" { 131 | name = "spoke02-default-route" 132 | resource_group_name = azurerm_resource_group.resource_group.name 133 | route_table_name = azurerm_route_table.route_table_spoke02.name 134 | address_prefix = "0.0.0.0/0" 135 | next_hop_type = "VirtualAppliance" 136 | next_hop_in_ip_address = cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 6) //module.paloalto_vmseries_01.interfaces["${var.firewall_vm_name}-01-trusted"].private_ip_address 137 | } 138 | 139 | resource "azurerm_subnet_route_table_association" "route_table_association_spoke02" { 140 | subnet_id = azurerm_subnet.subnet_spoke02_default.id 141 | route_table_id = azurerm_route_table.route_table_spoke02.id 142 | } -------------------------------------------------------------------------------- /scenario2/templates/output.tf: -------------------------------------------------------------------------------- 1 | output "paloalto_vmseries_01_dns" { 2 | value = "https://${azurerm_public_ip.fw01_mgmt_pip.fqdn}" 3 | } 4 | 5 | output "paloalto_vmseries_02_dns" { 6 | value = "https://${azurerm_public_ip.fw02_mgmt_pip.fqdn}" 7 | } 8 | 9 | output "paloalto_username" { 10 | value = var.username 11 | } 12 | 13 | # Use "terraform output paloalto_password" to get the password after terraform apply 14 | output "paloalto_password" { 15 | value = coalesce(var.password, random_password.password.result) 16 | sensitive = true 17 | } -------------------------------------------------------------------------------- /scenario2/templates/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = "=3.76" 6 | } 7 | 8 | http = { 9 | source = "hashicorp/http" 10 | version = "3.1.0" 11 | } 12 | } 13 | } 14 | 15 | provider "azurerm" { 16 | features { 17 | resource_group { 18 | prevent_deletion_if_contains_resources = false 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /scenario2/templates/variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | type = string 3 | default = "West Europe" 4 | } 5 | 6 | variable "resource_group_name" { 7 | type = string 8 | default = "rg-panfw-scenario2" 9 | } 10 | 11 | variable "vnet_hub_name" { 12 | type = string 13 | default = "hub-vnet" 14 | } 15 | 16 | variable "vnet_hub_address_space" { 17 | type = string 18 | default = "10.0.0.0/24" 19 | } 20 | 21 | variable "vnet_spoke01_name" { 22 | type = string 23 | default = "spoke01-vnet" 24 | } 25 | 26 | variable "vnet_spoke01_address_space" { 27 | type = string 28 | default = "10.0.1.0/24" 29 | } 30 | 31 | variable "vnet_spoke02_name" { 32 | type = string 33 | default = "spoke02-vnet" 34 | } 35 | 36 | variable "vnet_spoke02_address_space" { 37 | type = string 38 | default = "10.0.2.0/24" 39 | } 40 | 41 | variable "firewall_vm_name" { 42 | type = string 43 | default = "panfw-vm" 44 | } 45 | 46 | variable "allow_inbound_mgmt_ips" { 47 | default = "" 48 | type = string 49 | } 50 | 51 | variable "common_vmseries_sku" { 52 | description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" 53 | default = "byol" 54 | type = string 55 | } 56 | 57 | variable "common_vmseries_version" { 58 | description = "VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`" 59 | default = "latest" 60 | type = string 61 | } 62 | 63 | variable "common_vmseries_vm_size" { 64 | description = "Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported." 65 | default = "Standard_D3_v2" 66 | type = string 67 | } 68 | 69 | variable "username" { 70 | description = "Initial administrative username to use for all systems." 71 | default = "panadmin" 72 | type = string 73 | } 74 | 75 | variable "password" { 76 | description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." 77 | default = "Microsoft=1Microsoft=1" 78 | type = string 79 | } 80 | 81 | variable "avzones" { 82 | type = list(string) 83 | default = ["1", "2", "3"] 84 | } 85 | 86 | variable "enable_zones" { 87 | type = bool 88 | default = false 89 | } 90 | 91 | variable "vm_size" { 92 | type = string 93 | default = "Standard_DS1_v2" 94 | description = "VM Size" 95 | } 96 | 97 | variable "vm_os_publisher" { 98 | type = string 99 | default = "canonical" 100 | description = "VM OS Publisher" 101 | } 102 | 103 | variable "vm_os_offer" { 104 | type = string 105 | #default = "UbuntuServer" 106 | default = "0001-com-ubuntu-server-jammy" 107 | description = "VM OS Offer" 108 | } 109 | 110 | variable "vm_os_sku" { 111 | type = string 112 | default = "22_04-lts-gen2" 113 | description = "VM OS Sku" 114 | } 115 | 116 | variable "vm_os_version" { 117 | type = string 118 | default = "latest" 119 | description = "VM OS Version" 120 | } 121 | 122 | locals { 123 | custom_script_spoke01 = < To ensure proper routing and management of traffic, it is crucial to define two distinct Virtual Routers (Trusted and Untrusted) per firewall instance, as the Azure Internal Load Balancer and External Load Balancer rely on the same probing source IP address 168.63.129.16. 36 | 37 | Security Policies are set up to: 38 | * Permit traffic using the ICMP (ping) protocol within the trust zone 39 | * Allow HTTP and HTTPS protocol traffic from the `trust` zone to the `untrust` zone 40 | * Allow probing by Azure Load Balancer on both trusted and untrusted interfaces 41 | * Deny everything else 42 | 43 | ## Task 1: Deploy Templates 44 | 45 | To begin the Terraform deployment, following these steps: 46 | 47 | - Sign in to Azure Cloud shell at [https://shell.azure.com/](https://shell.azure.com/) or use your local terminal 48 | 49 | - Confirm that you are operating within the appropriate subscription by using: 50 | 51 | `az account show` 52 | 53 | - Accept the Azure Marketplace terms for the VM-Series images: 54 | 55 | `az vm image terms accept --publisher paloaltonetworks --offer vmseries-flex --plan byol --subscription MySubscription` 56 | 57 | - Clone the current GitHub repository with the command: 58 | 59 | `git clone https://github.com/davidsntg/microhack-azure-panfw` 60 | 61 | - Navigate to the new folder *microhack-azure-panfw/* and initialize the terraform modules with the commands: 62 | 63 | `cd microhack-azure-panfw/scenario3/templates` 64 | 65 | `terraform init` 66 | 67 | - Start the deployment by running: 68 | 69 | `terraform apply` 70 | 71 | - When prompted, confirm the start of the deployment by responding with a **yes** 72 | 73 | - Wait for the deployment to finish, which should take approximately 10 minutes 74 | 75 | ## Task 2: Enable your Public IP to Access the Palo Alto Consoles 76 | 77 | The Palo Alto administration console can be accessed via HTTPS, using the appliance's public management IP. 78 | 79 | During deployment, the public IP from which Terraform is executed provides access to the administration console. 80 | 81 | If this IP differs from the client's public IP accessing the administration console, the NSG `panfw-vm-mgmt-nsg` must be updated: 82 | 83 | ![img](docs/scenario3-allowmgmntnsgpubip.png) 84 | 85 | ## Task 3: Connect to the Palo Alto Consoles 86 | 87 | - **Open a web browser with two tabs** and navigate to the Palo Alto Consoles. The URL, username and password are given by the results of the previous `terraform apply`: 88 | 89 | ![img](docs/terraform_apply.png) 90 | 91 | Run the command `terraform output paloalto_password` to display the password in plain text. 92 | 93 | > **Note**: The Firewall may take between 5-10 minutes to start up. If the console does not appear, feel free to refresh the page. 94 | 95 | 96 | ## Task 4: HA Check - Step-by-Step shutdown VM-Series instances 97 | 98 | In this task, we will sequentially power down the VM-Series instances while generating traffic between the VM `spoke01-vm` and the VM `spoke02-vm`. 99 | 100 | This will allow us to confirm that there's no significant disruption in service. Here are the steps: 101 | 102 | * Open the 'Serial Console' of the VM `spoke02-vm` from the Azure portal 103 | * Login using the same login credentials as those used for the Palo Alto console 104 | * Initiate a ping to the VM `spoke01-vm`: `ping 10.0.1.4` 105 | 106 | ![](docs/scenario3-check-ha-ping.png) 107 | 108 | * Shutdown the VM `panfw-vm-01` 109 | * Check that the ping to VM `spoke01-vm` is still successful 110 | * Start the VM `panfw-vm-01`, wait for 5 minutes, then stop the VM `panfw-vm-02` 111 | * Again, verify that the ping to VM `spoke01-vm` is successful 112 | * Finally, start the VM `panfw-vm-01` 113 | 114 | * Observe the result of the previous operations in the metrics of the internal load balancer `panfw-trusted-ilb`: 115 | 116 | ![](docs/scenario3-check-ha-no-packets-loss.png) 117 | 118 | ## Task 5: DNAT Rule - Expose `spoke02-vm` on to the Internet 119 | 120 | In this task, we aim to make the `spoke02-vm` VM accessible from the internet. 121 | 122 | To do this, we will SSH the public IP `panfw-vm-elb-untrusted-pip`, which is attached to the external load balancer `panfw-untrusted-elb`, and set up a NAT rule to redirect the flow to spoke02-VM. A Security rule will then be created. 123 | 124 | On Azure's side, a load balacing rule has already been created on the external load balancer during terraform deployment: 125 | 126 | ![](docs/scenario3-dnat-elb-loadbalancingrule-ssh.png) 127 | 128 | A new rule just needs to be created on the NSG `panfw-vm-untrusted-nsg` to permit traffic from the internet on port 22: 129 | 130 | ![](docs/scenario3-dnat-nsg.png) 131 | 132 | Let's now configure the VM-Series firewalls. 133 | 134 | > Execute the following steps on the `panfw-vm-01` instance: 135 | 136 | * Navigate to the "NAT" tab under Policies and create a new rule: 137 | * General 138 | * Name: TEMP-NAT-SSH_Internet-Spoke02-VM 139 | 140 | ![](docs/scenario3-dnat-nat_rule_fw01-general.png) 141 | 142 | * Original Packet 143 | * Source zone: untrust 144 | * Destination zone: untrust 145 | * Destination interface: ethernet1/2 146 | * Service: SSH 147 | * Destination Address: 10.0.0.20 148 | 149 | ![](docs/scenario3-dnat-nat_rule_fw01-original.png) 150 | 151 | * Translated Packet 152 | * Source Address Translation 153 | * Type: Dynamic IP and Port 154 | * Address Type: Interface Address 155 | * Interface: ethernet1/1 156 | * Destination Address Translation: 157 | * Type: Static IP 158 | * Translated Address: 10.0.2.4 159 | 160 | ![](docs/scenario3-dnat-nat_rule_fw01-translated.png) 161 | 162 | * Go to the "Security" tab in Policies and create a new rule: 163 | * Name: A-TEMP-SSH-INTERNET-SPOKE02VM 164 | * Source Zone: untrust 165 | * Destination Zone: trust 166 | * Destination Address: 10.0.0.20/32 167 | * Service: SSH 168 | * Action: Allow 169 | 170 | * Position this rule prior to the "deny all" rule: 171 | 172 | ![](docs/scenario3-dnat-security_rule_fw01.png) 173 | 174 | * Click 'OK' and Commit changes 175 | 176 | > Execute the following steps on the `panfw-vm-02` instance: 177 | 178 | * Navigate to the "NAT" tab under Policies and create a new rule: 179 | * General 180 | * Name: TEMP-NAT-SSH_Internet-Spoke02-VM 181 | * Original Packet 182 | * Source zone: untrust 183 | * Destination zone: untrust 184 | * Destination interface: ethernet1/2 185 | * Service: SSH 186 | * Destination Address: 10.0.0.21 187 | * Translated Packet 188 | * Source Address Translation 189 | * Type: Dynamic IP and Port 190 | * Address Type: Interface Address 191 | * Interface: ethernet1/1 192 | * Destination Address Translation: 193 | * Type: Static IP 194 | * Translated Address: 10.0.2.4 195 | 196 | * Go to the "Security" tab in Policies and create a new rule: 197 | * Name: A-TEMP-SSH-INTERNET-SPOKE02VM 198 | * Source Zone: untrust 199 | * Destination Zone: trust 200 | * Destination Address: 10.0.0.21/32 201 | * Service: SSH 202 | * Action: Allow 203 | 204 | * Position this rule prior to the "deny all" rule: 205 | 206 | * Click 'OK' and Commit changes 207 | 208 | Finally, test that is works by accessing the public IP `panfw-vm-elb-untrusted-pip` associated to the external load balancer `panfw-untrusted-elb` from an SSH client on your machine. 209 | 210 | ![](docs/scenario3-dnat-ssh-success.png) 211 | 212 | 213 | ## Task 6: Internet breakout with NAT Gateway 214 | 215 | Currently, the VMs `spoke01-vm` and `spoke02-VM` are able to connect to the internet using a configured NAT rule (`NAT-INTERNET-OUT`) using the public IP `panfw-vm-elb-untrusted-pip` associated to the external load balancer `panfw-untrusted-elb`. 216 | 217 | It is possible to check that from `spoke02-vm`: 218 | ```bash 219 | spoke02-vm:~$ curl ifconfig.me 220 | 20.101.96.114 221 | ``` 222 | 223 | This internet breakout design has some limitation: an issue of SNAT port exhaustion can arise; the recommendation is to use a NAT Gateway. 224 | 225 | A NAT Gateway can mitigate [SNAT port exhaustion](https://azure.microsoft.com/en-us/blog/dive-deep-into-nat-gateway-s-snat-port-behavior/): 226 | * by providing a dynamic pool of SNAT ports, reducing the risk of connection failures. 227 | * by randomly selecting and reusing SNAT ports, preventing ports from being selected too quickly for the same destination. 228 | * by allowing for multiple connections at the same time to different destination endpoints even when all SNAT ports are in use. 229 | 230 | ---- 231 | 232 | * Go to Azure Portal, and search 'NAT Gateway' in the marketplace 233 | * Resource Group: `rg-panfw-scenario3` 234 | * NAT gateway name: panfw-untrusted-natgw 235 | * Region: West Europe 236 | 237 | ![](docs/scenario3-natgw-01.png) 238 | * Outbound IP 239 | * Public IP addresses: (New) panfw-untrusted-natgw-pip01 240 | 241 | ![](docs/scenario3-natgw-02.png) 242 | * Subnet: 243 | * VNet: hub-vnet 244 | * Subnet: pan-untrusted-subnet 245 | 246 | ![](docs/scenario3-natgw-03.png) 247 | 248 | After a couple of minutes, check again outgoing public IP from `spoke02-vm`: 249 | ```bash 250 | spoke02-vm:~$ curl ifconfig.me 251 | 40.91.212.148 252 | ``` 253 | 254 | `40.91.212.148` is indeed the public IP associated to the NAT Gateway: 255 | 256 | ![](docs/scenario3-natgw-04.png) 257 | 258 | ## 🏁 Results 259 | 260 | * Successfully deployed two instances of the VM-Series firewalls on Azure, both running as active instances. 261 | * Confirmed continuous service with no significant disruption during sequential shutdown of the VM-Series firewalls. 262 | * Successfully exposed the `spoke02-vm` VM to the internet by setting up a NAT rule and creating a security rule. 263 | * Successfully created a NAT Gateway to mitigate the risk of SNAT port exhaustion. Confirmed the outgoing public IP from `spoke02-vm` is indeed associated with the NAT Gateway. 264 | 265 | ## Notes 266 | 267 | In a production environment: 268 | * VM-Series instances should be distributed across Availability Zones or inside an Availability Set 269 | * The use of Panorama simplifies the firewall configuration by ensuring that policy updates are automatically applied to both instances 270 | 271 | ## Appendix: 272 | 273 | * [YouTube - Active/Active NVA on Azure with HaPorts (Palo Alto and SAP RISE)](https://www.youtube.com/watch?v=uGFEJoZgq0U) 274 | 275 | ### [>> GO TO SCENARIO #4](../scenario4/README.md) 276 | -------------------------------------------------------------------------------- /scenario3/docs/scenario3-allowmgmntnsgpubip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-allowmgmntnsgpubip.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-architecture.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-check-ha-no-packets-loss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-check-ha-no-packets-loss.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-check-ha-ping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-check-ha-ping.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-dnat-elb-loadbalancingrule-ssh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-dnat-elb-loadbalancingrule-ssh.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-dnat-nat_rule_fw01-general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-dnat-nat_rule_fw01-general.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-dnat-nat_rule_fw01-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-dnat-nat_rule_fw01-original.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-dnat-nat_rule_fw01-translated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-dnat-nat_rule_fw01-translated.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-dnat-nsg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-dnat-nsg.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-dnat-security_rule_fw01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-dnat-security_rule_fw01.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-dnat-ssh-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-dnat-ssh-success.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-ifconfig-natgw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-ifconfig-natgw.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-ifconfig-pip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-ifconfig-pip.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-natgw-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-natgw-01.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-natgw-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-natgw-02.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-natgw-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-natgw-03.png -------------------------------------------------------------------------------- /scenario3/docs/scenario3-natgw-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/scenario3-natgw-04.png -------------------------------------------------------------------------------- /scenario3/docs/terraform_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario3/docs/terraform_apply.png -------------------------------------------------------------------------------- /scenario3/templates/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | .terraform.lock.hcl 8 | 9 | # Crash log files 10 | crash.log 11 | 12 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 13 | # .tfvars files are managed as part of configuration and so should be included in 14 | # version control. 15 | # 16 | # example.tfvars 17 | 18 | # Ignore override files as they are usually used to override resources locally and so 19 | # are not checked in 20 | override.tf 21 | override.tf.json 22 | *_override.tf 23 | *_override.tf.json 24 | 25 | # Include override files you do wish to add to version control using negated pattern 26 | # 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* -------------------------------------------------------------------------------- /scenario3/templates/files/init-cfg.txt: -------------------------------------------------------------------------------- 1 | hostname=active-active-elb-ilb -------------------------------------------------------------------------------- /scenario3/templates/firewall.tf: -------------------------------------------------------------------------------- 1 | resource "random_integer" "id" { 2 | min = 100 3 | max = 999 4 | } 5 | 6 | resource "random_integer" "id2" { 7 | min = 100 8 | max = 999 9 | } 10 | 11 | resource "random_password" "password" { 12 | length = 16 13 | min_lower = 1 14 | min_numeric = 1 15 | min_special = 1 16 | min_upper = 1 17 | } 18 | 19 | # Allow inbound access to Management subnet. 20 | 21 | data "http" "ipinfo" { 22 | url = "https://ifconfig.me" 23 | } 24 | 25 | resource "azurerm_public_ip" "fws_untrusted_pip" { 26 | name = "${var.firewall_vm_name}-elb-untrusted-pip" 27 | location = azurerm_resource_group.resource_group.location 28 | resource_group_name = azurerm_resource_group.resource_group.name 29 | allocation_method = "Static" 30 | sku = "Standard" 31 | } 32 | 33 | # Firewall #1 34 | 35 | resource "azurerm_public_ip" "fw01_mgmt_pip" { 36 | name = "${var.firewall_vm_name}-01-mgmt-pip" 37 | location = azurerm_resource_group.resource_group.location 38 | resource_group_name = azurerm_resource_group.resource_group.name 39 | allocation_method = "Static" 40 | sku = "Standard" 41 | domain_name_label = "${var.firewall_vm_name}-mgmt-01-${random_integer.id.result}" 42 | } 43 | 44 | resource "local_file" "bootstrap01" { 45 | 46 | content = templatefile("${path.module}/files/bootstrap.tpl", { 47 | interface-trust = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 4)}/32" 48 | interface-untrust = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 4)}/32" 49 | next-hop-trusted = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 1)}" 50 | next-hop-untrusted = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 1)}" 51 | }) 52 | filename = "${path.module}/files/bootstrap01.xml" 53 | 54 | } 55 | 56 | module "bootstrap01" { 57 | source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/bootstrap" 58 | version = "1.2.0" 59 | 60 | name = "paloaltobootstrap${random_integer.id.result}" 61 | location = azurerm_resource_group.resource_group.location 62 | resource_group_name = azurerm_resource_group.resource_group.name 63 | storage_share_name = "sharepaloaltobootstrap${random_integer.id.result}" 64 | storage_acl = false 65 | 66 | files = { 67 | "files/init-cfg.txt" = "config/init-cfg.txt" 68 | "files/bootstrap01.xml" = "config/bootstrap.xml" 69 | } 70 | files_md5 = { 71 | "files/init-cfg.txt" = md5(file("files/init-cfg.txt")) 72 | "files/bootstrap01.xml" = md5(local_file.bootstrap01.content) 73 | } 74 | 75 | depends_on = [local_file.bootstrap01] 76 | } 77 | 78 | module "paloalto_vmseries_01" { 79 | source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/vmseries" 80 | version = "1.2.0" 81 | 82 | location = azurerm_resource_group.resource_group.location 83 | resource_group_name = azurerm_resource_group.resource_group.name 84 | name = "${var.firewall_vm_name}-01" 85 | username = var.username 86 | password = coalesce(var.password, random_password.password.result) 87 | img_version = var.common_vmseries_version 88 | img_sku = var.common_vmseries_sku 89 | vm_size = var.common_vmseries_vm_size 90 | enable_zones = var.enable_zones 91 | bootstrap_options = (join(",", 92 | [ 93 | "storage-account=${module.bootstrap01.storage_account.name}", 94 | "access-key=${module.bootstrap01.storage_account.primary_access_key}", 95 | "file-share=${module.bootstrap01.storage_share.name}", 96 | "share-directory=None" 97 | ] 98 | )) 99 | interfaces = [ 100 | { 101 | name = "${var.firewall_vm_name}-01-mgmt" 102 | subnet_id = azurerm_subnet.subnet_pan_mgmt.id 103 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_mgmt.address_prefixes[0], 4) 104 | public_ip_name = azurerm_public_ip.fw01_mgmt_pip.name 105 | public_ip_resource_group = azurerm_public_ip.fw01_mgmt_pip.resource_group_name 106 | }, 107 | { 108 | name = "${var.firewall_vm_name}-01-trusted" 109 | subnet_id = azurerm_subnet.subnet_pan_trusted.id 110 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 4) 111 | }, 112 | { 113 | name = "${var.firewall_vm_name}-01-untrusted" 114 | subnet_id = azurerm_subnet.subnet_pan_untrusted.id 115 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 4) 116 | } 117 | ] 118 | depends_on = [module.bootstrap01] 119 | } 120 | 121 | # Firewall #2 122 | 123 | resource "azurerm_public_ip" "fw02_mgmt_pip" { 124 | name = "${var.firewall_vm_name}-02-mgmt-pip" 125 | location = azurerm_resource_group.resource_group.location 126 | resource_group_name = azurerm_resource_group.resource_group.name 127 | allocation_method = "Static" 128 | sku = "Standard" 129 | domain_name_label = "${var.firewall_vm_name}-mgmt-02-${random_integer.id.result}" 130 | } 131 | 132 | resource "local_file" "bootstrap02" { 133 | 134 | content = templatefile("${path.module}/files/bootstrap.tpl", { 135 | interface-trust = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 5)}/32" 136 | interface-untrust = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 5)}/32" 137 | next-hop-trusted = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 1)}" 138 | next-hop-untrusted = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 1)}" 139 | }) 140 | filename = "${path.module}/files/bootstrap02.xml" 141 | 142 | } 143 | 144 | module "bootstrap02" { 145 | source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/bootstrap" 146 | version = "1.2.0" 147 | 148 | name = "paloaltobootstrap${random_integer.id2.result}" 149 | location = azurerm_resource_group.resource_group.location 150 | resource_group_name = azurerm_resource_group.resource_group.name 151 | storage_share_name = "sharepaloaltobootstrap${random_integer.id2.result}" 152 | storage_acl = false 153 | 154 | files = { 155 | "files/init-cfg.txt" = "config/init-cfg.txt" 156 | "files/bootstrap02.xml" = "config/bootstrap.xml" 157 | } 158 | files_md5 = { 159 | "files/init-cfg.txt" = md5(file("files/init-cfg.txt")) 160 | "files/bootstrap02.xml" = md5(local_file.bootstrap02.content) 161 | } 162 | 163 | depends_on = [local_file.bootstrap02] 164 | } 165 | 166 | module "paloalto_vmseries_02" { 167 | source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/vmseries" 168 | version = "1.2.0" 169 | 170 | location = azurerm_resource_group.resource_group.location 171 | resource_group_name = azurerm_resource_group.resource_group.name 172 | name = "${var.firewall_vm_name}-02" 173 | username = var.username 174 | password = coalesce(var.password, random_password.password.result) 175 | img_version = var.common_vmseries_version 176 | img_sku = var.common_vmseries_sku 177 | vm_size = var.common_vmseries_vm_size 178 | enable_zones = var.enable_zones 179 | bootstrap_options = (join(",", 180 | [ 181 | "storage-account=${module.bootstrap02.storage_account.name}", 182 | "access-key=${module.bootstrap02.storage_account.primary_access_key}", 183 | "file-share=${module.bootstrap02.storage_share.name}", 184 | "share-directory=None" 185 | ] 186 | )) 187 | interfaces = [ 188 | { 189 | name = "${var.firewall_vm_name}-02-mgmt" 190 | subnet_id = azurerm_subnet.subnet_pan_mgmt.id 191 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_mgmt.address_prefixes[0], 5) 192 | public_ip_name = azurerm_public_ip.fw02_mgmt_pip.name 193 | public_ip_resource_group = azurerm_public_ip.fw02_mgmt_pip.resource_group_name 194 | }, 195 | { 196 | name = "${var.firewall_vm_name}-02-trusted" 197 | subnet_id = azurerm_subnet.subnet_pan_trusted.id 198 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 5) 199 | }, 200 | { 201 | name = "${var.firewall_vm_name}-02-untrusted" 202 | subnet_id = azurerm_subnet.subnet_pan_untrusted.id 203 | private_ip_address = cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 5) 204 | } 205 | ] 206 | depends_on = [module.bootstrap02] 207 | } 208 | 209 | # NSGs 210 | 211 | resource "azurerm_network_security_group" "fws-mgmt-nsg" { 212 | name = "${var.firewall_vm_name}-mgmt-nsg" 213 | location = azurerm_resource_group.resource_group.location 214 | resource_group_name = azurerm_resource_group.resource_group.name 215 | } 216 | 217 | resource "azurerm_network_security_rule" "network_security_rule_mgmt" { 218 | name = "mgmt-allow-inbound" 219 | resource_group_name = azurerm_resource_group.resource_group.name 220 | network_security_group_name = azurerm_network_security_group.fws-mgmt-nsg.name 221 | access = "Allow" 222 | direction = "Inbound" 223 | priority = 1000 224 | protocol = "Tcp" 225 | source_port_range = "*" 226 | source_address_prefixes = [coalesce(var.allow_inbound_mgmt_ips, data.http.ipinfo.response_body)] 227 | destination_address_prefix = "*" 228 | destination_port_range = "443" 229 | } 230 | 231 | resource "azurerm_subnet_network_security_group_association" "network_security_group_association_mgmt" { 232 | subnet_id = azurerm_subnet.subnet_pan_mgmt.id 233 | network_security_group_id = azurerm_network_security_group.fws-mgmt-nsg.id 234 | } 235 | 236 | resource "azurerm_network_security_group" "fws-untrusted-nsg" { 237 | name = "${var.firewall_vm_name}-untrusted-nsg" 238 | location = azurerm_resource_group.resource_group.location 239 | resource_group_name = azurerm_resource_group.resource_group.name 240 | } 241 | 242 | resource "azurerm_network_security_rule" "network_security_rule_untrusted" { 243 | name = "untrusted-allow-inbound" 244 | resource_group_name = azurerm_resource_group.resource_group.name 245 | network_security_group_name = azurerm_network_security_group.fws-untrusted-nsg.name 246 | access = "Allow" 247 | direction = "Inbound" 248 | priority = 1000 249 | protocol = "*" 250 | source_port_range = "*" 251 | source_address_prefix = "*" 252 | destination_address_prefix = module.paloalto_vmseries_01.interfaces["${var.firewall_vm_name}-01-untrusted"].private_ip_address 253 | destination_port_range = "*" 254 | } 255 | 256 | resource "azurerm_subnet_network_security_group_association" "network_security_group_association_untrusted" { 257 | subnet_id = azurerm_subnet.subnet_pan_untrusted.id 258 | network_security_group_id = azurerm_network_security_group.fws-untrusted-nsg.id 259 | } -------------------------------------------------------------------------------- /scenario3/templates/lbs.tf: -------------------------------------------------------------------------------- 1 | # Internal Load Balancer - Trusted 2 | 3 | resource "azurerm_lb" "trusted_ilb" { 4 | resource_group_name = azurerm_resource_group.resource_group.name 5 | location = azurerm_resource_group.resource_group.location 6 | name = "panfw-trusted-ilb" 7 | sku = "Standard" 8 | frontend_ip_configuration { 9 | name = "panfw-trusted-ilb-ip" 10 | subnet_id = azurerm_subnet.subnet_pan_trusted.id 11 | } 12 | depends_on = [module.paloalto_vmseries_01, module.paloalto_vmseries_02] 13 | } 14 | 15 | resource "azurerm_lb_backend_address_pool" "trusted_ilb_backendpool" { 16 | name = "trusted_ilb_backend-pool" 17 | loadbalancer_id = azurerm_lb.trusted_ilb.id 18 | } 19 | 20 | resource "azurerm_network_interface_backend_address_pool_association" "trusted_01" { 21 | network_interface_id = module.paloalto_vmseries_01.interfaces["${var.firewall_vm_name}-01-trusted"].id 22 | ip_configuration_name = "primary" 23 | backend_address_pool_id = azurerm_lb_backend_address_pool.trusted_ilb_backendpool.id 24 | } 25 | 26 | resource "azurerm_network_interface_backend_address_pool_association" "trusted_02" { 27 | network_interface_id = module.paloalto_vmseries_02.interfaces["${var.firewall_vm_name}-02-trusted"].id 28 | ip_configuration_name = "primary" 29 | backend_address_pool_id = azurerm_lb_backend_address_pool.trusted_ilb_backendpool.id 30 | } 31 | 32 | resource "azurerm_lb_probe" "trusted_ilb_probe" { 33 | loadbalancer_id = azurerm_lb.trusted_ilb.id 34 | name = "trusted-ilb-probe" 35 | port = 443 36 | protocol = "Https" 37 | request_path = "/php/login.php" 38 | interval_in_seconds = 5 39 | number_of_probes = 2 40 | } 41 | 42 | resource "azurerm_lb_rule" "trusted_ilb_rules" { 43 | name = "all-ports" 44 | loadbalancer_id = azurerm_lb.trusted_ilb.id 45 | protocol = "All" 46 | frontend_port = 0 47 | backend_port = 0 48 | frontend_ip_configuration_name = "panfw-trusted-ilb-ip" 49 | idle_timeout_in_minutes = 5 50 | backend_address_pool_ids = [azurerm_lb_backend_address_pool.trusted_ilb_backendpool.id] 51 | probe_id = azurerm_lb_probe.trusted_ilb_probe.id 52 | } 53 | 54 | # External Load Balancer - Untrusted 55 | 56 | resource "azurerm_lb" "untrusted_elb" { 57 | resource_group_name = azurerm_resource_group.resource_group.name 58 | location = azurerm_resource_group.resource_group.location 59 | name = "panfw-untrusted-elb" 60 | sku = "Standard" 61 | frontend_ip_configuration { 62 | name = "panfw-untrusted-elb-ip" 63 | public_ip_address_id = azurerm_public_ip.fws_untrusted_pip.id 64 | } 65 | depends_on = [module.paloalto_vmseries_01, module.paloalto_vmseries_02] 66 | } 67 | 68 | resource "azurerm_lb_backend_address_pool" "untrusted_elb_backendpool" { 69 | name = "untrusted_elb_backend-pool" 70 | loadbalancer_id = azurerm_lb.untrusted_elb.id 71 | } 72 | 73 | resource "azurerm_network_interface_backend_address_pool_association" "untrusted_01" { 74 | network_interface_id = module.paloalto_vmseries_01.interfaces["${var.firewall_vm_name}-01-untrusted"].id 75 | ip_configuration_name = "primary" 76 | backend_address_pool_id = azurerm_lb_backend_address_pool.untrusted_elb_backendpool.id 77 | } 78 | 79 | resource "azurerm_network_interface_backend_address_pool_association" "untrusted_02" { 80 | network_interface_id = module.paloalto_vmseries_02.interfaces["${var.firewall_vm_name}-02-untrusted"].id 81 | ip_configuration_name = "primary" 82 | backend_address_pool_id = azurerm_lb_backend_address_pool.untrusted_elb_backendpool.id 83 | } 84 | 85 | resource "azurerm_lb_probe" "untrusted_elb_probe" { 86 | loadbalancer_id = azurerm_lb.untrusted_elb.id 87 | name = "untrusted-elb-probe" 88 | port = 22 89 | protocol = "Tcp" 90 | interval_in_seconds = 5 91 | number_of_probes = 2 92 | } 93 | 94 | resource "azurerm_lb_rule" "untrusted_elb_rules" { 95 | name = "TCP-22" 96 | loadbalancer_id = azurerm_lb.untrusted_elb.id 97 | protocol = "Tcp" 98 | frontend_port = 22 99 | backend_port = 22 100 | frontend_ip_configuration_name = "panfw-untrusted-elb-ip" 101 | idle_timeout_in_minutes = 5 102 | backend_address_pool_ids = [azurerm_lb_backend_address_pool.untrusted_elb_backendpool.id] 103 | probe_id = azurerm_lb_probe.untrusted_elb_probe.id 104 | disable_outbound_snat = true 105 | } 106 | 107 | resource "azurerm_lb_outbound_rule" "untrusted_elb_outbound_rules" { 108 | name = "untrusted-elb-outbound-rule" 109 | loadbalancer_id = azurerm_lb.untrusted_elb.id 110 | frontend_ip_configuration { 111 | name = "panfw-untrusted-elb-ip" 112 | } 113 | allocated_outbound_ports = 1000 114 | idle_timeout_in_minutes = 5 115 | protocol = "All" 116 | backend_address_pool_id = azurerm_lb_backend_address_pool.untrusted_elb_backendpool.id 117 | } 118 | -------------------------------------------------------------------------------- /scenario3/templates/main.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_marketplace_agreement" "paloaltonetworks" { 2 | publisher = "paloaltonetworks" 3 | offer = "vmseries-flex" 4 | plan = "byol" 5 | } 6 | 7 | resource "azurerm_marketplace_agreement" "paloaltonetworks" { 8 | count = data.azurerm_marketplace_agreement.paloaltonetworks.id == null ? 1 : 0 9 | publisher = "paloaltonetworks" 10 | offer = "vmseries-flex" 11 | plan = "byol" 12 | } 13 | 14 | resource "azurerm_resource_group" "resource_group" { 15 | name = var.resource_group_name 16 | location = var.location 17 | } -------------------------------------------------------------------------------- /scenario3/templates/network.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_virtual_network" "virtual_network_hub" { 2 | name = var.vnet_hub_name 3 | address_space = [var.vnet_hub_address_space] 4 | location = azurerm_resource_group.resource_group.location 5 | resource_group_name = azurerm_resource_group.resource_group.name 6 | } 7 | 8 | resource "azurerm_subnet" "subnet_pan_mgmt" { 9 | name = "pan-mgmt-subnet" 10 | resource_group_name = azurerm_resource_group.resource_group.name 11 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 12 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 0)] 13 | } 14 | 15 | resource "azurerm_subnet" "subnet_pan_untrusted" { 16 | name = "pan-untrusted-subnet" 17 | resource_group_name = azurerm_resource_group.resource_group.name 18 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 19 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 1)] 20 | } 21 | 22 | resource "azurerm_subnet" "subnet_pan_trusted" { 23 | name = "pan-trusted-subnet" 24 | resource_group_name = azurerm_resource_group.resource_group.name 25 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 26 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 2)] 27 | } 28 | 29 | resource "azurerm_virtual_network" "virtual_network_spoke01" { 30 | name = var.vnet_spoke01_name 31 | address_space = [var.vnet_spoke01_address_space] 32 | location = azurerm_resource_group.resource_group.location 33 | resource_group_name = azurerm_resource_group.resource_group.name 34 | } 35 | 36 | resource "azurerm_subnet" "subnet_spoke01_default" { 37 | name = "snet-default" 38 | resource_group_name = azurerm_resource_group.resource_group.name 39 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke01.name 40 | address_prefixes = [cidrsubnet(var.vnet_spoke01_address_space, 4, 0)] 41 | } 42 | 43 | resource "azurerm_virtual_network_peering" "peering_hub_to_spoke01" { 44 | name = "hub-to-spoke01" 45 | resource_group_name = azurerm_resource_group.resource_group.name 46 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 47 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_spoke01.id 48 | allow_virtual_network_access = true 49 | allow_forwarded_traffic = true 50 | } 51 | 52 | resource "azurerm_virtual_network_peering" "peering_spoke01_to_hub" { 53 | name = "spoke01-to-hub" 54 | resource_group_name = azurerm_resource_group.resource_group.name 55 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke01.name 56 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_hub.id 57 | allow_virtual_network_access = true 58 | allow_forwarded_traffic = true 59 | } 60 | 61 | resource "azurerm_virtual_network" "virtual_network_spoke02" { 62 | name = var.vnet_spoke02_name 63 | address_space = [var.vnet_spoke02_address_space] 64 | location = azurerm_resource_group.resource_group.location 65 | resource_group_name = azurerm_resource_group.resource_group.name 66 | } 67 | 68 | resource "azurerm_subnet" "subnet_spoke02_default" { 69 | name = "snet-default" 70 | resource_group_name = azurerm_resource_group.resource_group.name 71 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke02.name 72 | address_prefixes = [cidrsubnet(var.vnet_spoke02_address_space, 4, 0)] 73 | } 74 | 75 | resource "azurerm_virtual_network_peering" "peering_hub_to_spoke02" { 76 | name = "hub-to-spoke02" 77 | resource_group_name = azurerm_resource_group.resource_group.name 78 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 79 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_spoke02.id 80 | allow_virtual_network_access = true 81 | allow_forwarded_traffic = true 82 | } 83 | 84 | resource "azurerm_virtual_network_peering" "peering_spoke02_to_hub" { 85 | name = "spoke02-to-hub" 86 | resource_group_name = azurerm_resource_group.resource_group.name 87 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke02.name 88 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_hub.id 89 | allow_virtual_network_access = true 90 | allow_forwarded_traffic = true 91 | } 92 | 93 | resource "azurerm_route_table" "route_table_spoke01" { 94 | name = "spoke01-rt" 95 | location = azurerm_resource_group.resource_group.location 96 | resource_group_name = azurerm_resource_group.resource_group.name 97 | disable_bgp_route_propagation = true 98 | } 99 | 100 | resource "azurerm_route" "route_spoke01_default" { 101 | name = "spoke01-default-route" 102 | resource_group_name = azurerm_resource_group.resource_group.name 103 | route_table_name = azurerm_route_table.route_table_spoke01.name 104 | address_prefix = "0.0.0.0/0" 105 | next_hop_type = "VirtualAppliance" 106 | next_hop_in_ip_address = azurerm_lb.trusted_ilb.private_ip_address 107 | } 108 | 109 | resource "azurerm_subnet_route_table_association" "route_table_association_spoke01" { 110 | subnet_id = azurerm_subnet.subnet_spoke01_default.id 111 | route_table_id = azurerm_route_table.route_table_spoke01.id 112 | } 113 | 114 | 115 | resource "azurerm_route_table" "route_table_spoke02" { 116 | name = "spoke02-rt" 117 | location = azurerm_resource_group.resource_group.location 118 | resource_group_name = azurerm_resource_group.resource_group.name 119 | disable_bgp_route_propagation = true 120 | } 121 | 122 | resource "azurerm_route" "route_spoke02_default" { 123 | name = "spoke02-default-route" 124 | resource_group_name = azurerm_resource_group.resource_group.name 125 | route_table_name = azurerm_route_table.route_table_spoke02.name 126 | address_prefix = "0.0.0.0/0" 127 | next_hop_type = "VirtualAppliance" 128 | next_hop_in_ip_address = azurerm_lb.trusted_ilb.private_ip_address 129 | } 130 | 131 | resource "azurerm_subnet_route_table_association" "route_table_association_spoke02" { 132 | subnet_id = azurerm_subnet.subnet_spoke02_default.id 133 | route_table_id = azurerm_route_table.route_table_spoke02.id 134 | } -------------------------------------------------------------------------------- /scenario3/templates/output.tf: -------------------------------------------------------------------------------- 1 | output "paloalto_vmseries_01_dns" { 2 | value = "https://${azurerm_public_ip.fw01_mgmt_pip.fqdn}" 3 | } 4 | 5 | output "paloalto_vmseries_02_dns" { 6 | value = "https://${azurerm_public_ip.fw02_mgmt_pip.fqdn}" 7 | } 8 | 9 | output "paloalto_username" { 10 | value = var.username 11 | } 12 | 13 | # Use "terraform output paloalto_password" to get the password after terraform apply 14 | output "paloalto_password" { 15 | value = coalesce(var.password, random_password.password.result) 16 | sensitive = true 17 | } -------------------------------------------------------------------------------- /scenario3/templates/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = "=3.76" 6 | } 7 | 8 | http = { 9 | source = "hashicorp/http" 10 | version = "3.1.0" 11 | } 12 | } 13 | } 14 | 15 | provider "azurerm" { 16 | features { 17 | resource_group { 18 | prevent_deletion_if_contains_resources = false 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /scenario3/templates/variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | type = string 3 | default = "West Europe" 4 | } 5 | 6 | variable "resource_group_name" { 7 | type = string 8 | default = "rg-panfw-scenario3" 9 | } 10 | 11 | variable "vnet_hub_name" { 12 | type = string 13 | default = "hub-vnet" 14 | } 15 | 16 | variable "vnet_hub_address_space" { 17 | type = string 18 | default = "10.0.0.0/24" 19 | } 20 | 21 | variable "vnet_spoke01_name" { 22 | type = string 23 | default = "spoke01-vnet" 24 | } 25 | 26 | variable "vnet_spoke01_address_space" { 27 | type = string 28 | default = "10.0.1.0/24" 29 | } 30 | 31 | variable "vnet_spoke02_name" { 32 | type = string 33 | default = "spoke02-vnet" 34 | } 35 | 36 | variable "vnet_spoke02_address_space" { 37 | type = string 38 | default = "10.0.2.0/24" 39 | } 40 | 41 | variable "firewall_vm_name" { 42 | type = string 43 | default = "panfw-vm" 44 | } 45 | 46 | variable "allow_inbound_mgmt_ips" { 47 | default = "" 48 | type = string 49 | } 50 | 51 | variable "common_vmseries_sku" { 52 | description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" 53 | default = "byol" 54 | type = string 55 | } 56 | 57 | variable "common_vmseries_version" { 58 | description = "VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`" 59 | default = "latest" 60 | type = string 61 | } 62 | 63 | variable "common_vmseries_vm_size" { 64 | description = "Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported." 65 | default = "Standard_D3_v2" 66 | type = string 67 | } 68 | 69 | variable "username" { 70 | description = "Initial administrative username to use for all systems." 71 | default = "panadmin" 72 | type = string 73 | } 74 | 75 | variable "password" { 76 | description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." 77 | default = "Microsoft=1Microsoft=1" 78 | type = string 79 | } 80 | 81 | variable "avzones" { 82 | type = list(string) 83 | default = ["1", "2", "3"] 84 | } 85 | 86 | variable "enable_zones" { 87 | type = bool 88 | default = false 89 | } 90 | 91 | variable "vm_size" { 92 | type = string 93 | default = "Standard_DS1_v2" 94 | description = "VM Size" 95 | } 96 | 97 | variable "vm_os_publisher" { 98 | type = string 99 | default = "canonical" 100 | description = "VM OS Publisher" 101 | } 102 | 103 | variable "vm_os_offer" { 104 | type = string 105 | #default = "UbuntuServer" 106 | default = "0001-com-ubuntu-server-jammy" 107 | description = "VM OS Offer" 108 | } 109 | 110 | variable "vm_os_sku" { 111 | type = string 112 | default = "22_04-lts-gen2" 113 | description = "VM OS Sku" 114 | } 115 | 116 | variable "vm_os_version" { 117 | type = string 118 | default = "latest" 119 | description = "VM OS Version" 120 | } 121 | 122 | locals { 123 | custom_script_spoke01 = < To ensure proper routing and management of traffic, it is crucial to define two distinct Virtual Routers (Trusted and Untrusted) per firewall instance, as the Azure Internal Load Balancer and External Load Balancer rely on the same probing source IP address 168.63.129.16. 36 | 37 | Security Policies are set up to: 38 | * Permit traffic using the ICMP (ping) protocol within the trust zone 39 | * Allow HTTP and HTTPS protocol traffic from the `trust` zone to the `untrust` zone 40 | * Allow probing by Azure Load Balancer on both trusted and untrusted interfaces 41 | * Permit traffic from the `trust` zone to the `trust` zone 42 | * Deny everything else 43 | 44 | ### VMSS configuration 45 | 46 | By default, the VMSS contains **only a single instance**. 47 | 48 | In this MicroHack, we will set up the scale-out and scale-in rules and test the system under increased load. 49 | 50 | ## Task 1: Deploy Templates 51 | 52 | To begin the Terraform deployment, following these steps: 53 | 54 | - Sign in to Azure Cloud shell at [https://shell.azure.com/](https://shell.azure.com/) or use your local terminal 55 | 56 | - Confirm that you are operating within the appropriate subscription by using: 57 | 58 | `az account show` 59 | 60 | - Accept the Azure Marketplace terms for the VM-Series images: 61 | 62 | `az vm image terms accept --publisher paloaltonetworks --offer vmseries-flex --plan byol --subscription MySubscription` 63 | 64 | - Clone the current GitHub repository with the command: 65 | 66 | `git clone https://github.com/davidsntg/microhack-azure-panfw` 67 | 68 | - Navigate to the new folder *microhack-azure-panfw/* and initialize the terraform modules with the commands: 69 | 70 | `cd microhack-azure-panfw/scenario4/templates` 71 | 72 | `terraform init` 73 | 74 | - Start the deployment by running: 75 | 76 | `terraform apply` 77 | 78 | - When prompted, confirm the start of the deployment by responding with a **yes**. 79 | 80 | - Wait for the deployment to finish, which should take approximately 10 minutes. 81 | 82 | ## Task 2: Enable your Public IP to Access the Palo Alto Consoles 83 | 84 | The Palo Alto administration console can be accessed via HTTPS, using the appliance's public management IP. 85 | 86 | During deployment, the public IP from which Terraform is executed provides access to the administration console. 87 | 88 | If this IP differs from the client's public IP accessing the administration console, the NSG `panfw-vm-mgmt-nsg` must be updated: 89 | 90 | ![img](docs/scenario4-allowmgmntnsgpubip.png) 91 | 92 | ## Task 3: Connect to the Palo Alto Consoles 93 | 94 | - **Open a web browser with two tabs** and navigate to the Palo Alto Consoles. The URL, username and password are given by the results of the previous `terraform apply`: 95 | 96 | ![img](docs/terraform_apply.png) 97 | 98 | Run the command `terraform output paloalto_password` to display the password in plain text. 99 | 100 | > **Note**: The Firewall may take between 5-10 minutes to start up. If the console does not appear, feel free to refresh the page. 101 | 102 | 103 | ## Task 4: Configure VMSS autoscale based on metrics 104 | 105 | * In `rg-panfw-scenario4` resource group, select `panfw-vmss` VMSS and navigate to the 'Scaling' tab 106 | * Configure the Custom autoscale policy: 107 | * Scale based on a metric: 108 | * Scale out: When panfw-vmss (Average) Percentage CPU > 60 (10 minutes) => Increase count by 1 109 | * Scale in: When panfw-vmss (Average) Percentage CPU < 40 (during 10 minutes) => Decrease count by 1 110 | * Minimum instance: 1 111 | * Maximum: 10 112 | * Default: 1 113 | 114 | ![](docs/scenario4-scaling-policy.png) 115 | 116 | * Save the Custom autoscale policy 117 | 118 | ## Task 5: Observe VMSS scale out 119 | 120 | In this task, the CPU of the VMSS instance will be stressed to observe the scale-out process and the subsequent deployment of multiple instances. 121 | 122 | This will be achieved using the `iperf` tool, installed on five VMs in `spoke01-vnet` and another five VMs in `spoke02-vnet`: 123 | * The VMs in spoke02-vnet will act as the `iperf` servers 124 | * `iperf` server is already running on these VMs 125 | * The VMs in spoke01-vnet will act as the `iperf` clients 126 | 127 | Here are the steps: 128 | * Launch five separate tabs in the browser 129 | * For each tab, access the 'Serial Console' on the respective VMs - `spoke01-vm01`, `spoke01-vm02`, `spoke01-vm03`, `spoke01-vm04` and `spoke01-vm05` 130 | * Login using the same login credentials as those used for the Palo Alto console 131 | * Begin the stress test: 132 | * From `spoke01-vm01`, execute `iperf -c 10.0.2.4 -t 3600 -P 10` 133 | * From `spoke01-vm02`, execute `iperf -c 10.0.2.5 -t 3600 -P 10` 134 | * From `spoke01-vm03`, execute `iperf -c 10.0.2.6 -t 3600 -P 10` 135 | * From `spoke01-vm04`, execute `iperf -c 10.0.2.7 -t 3600 -P 10` 136 | * From `spoke01-vm05`, execute `iperf -c 10.0.2.8 -t 3600 -P 10` 137 | 138 | * Open an another tab and navigate to the `panfw-vmss` VMSS 139 | * Display 'Monitoring' metrics and observe CPU increase: 140 | 141 | ![](docs/scenario4-scaling-cpu.png) 142 | 143 | * Wait ~10 minutes and observe the creation on new Instances in 'Instances' tab: 144 | 145 | ![](docs/scenario4-scaling-newinstances.png) 146 | 147 | > We can observe that multiple instances are under creation. This is because [overprovisioning](https://learn.microsoft.com/en-us/azure/virtual-machine-scale-sets/virtual-machine-scale-sets-design-overview#overprovisioning) is enabled: the scale set actually spins up more VMs than you asked for, then deletes the extra VMs once the requested number of VMs are successfully provisioned. 148 | 149 | ![](docs/scenario4-scaling-newinstances2.png) 150 | 151 | * Display again the CPU (average) of the VMSS: 152 | 153 | ![](docs/scenario4-scaling-cpu2.png) 154 | 155 | ## Task 6: Observe VMSS scale in 156 | 157 | * Terminate the stress test by pressing `CTRL+C` in each of the five Serial Consoles 158 | * Wait ~10 minutes 159 | * In 'Scaling' tab, go to 'Run history' tab: 160 | 161 | ![](docs/scenario4-scaling-runhistory.png) 162 | 163 | This scale in was trigger by the scale-in policy configured && triggered before: 164 | 165 | ![](docs/scenario4-scaling-cpu3.png) 166 | 167 | ## 🏁 Results 168 | 169 | * Successfully deployed using Azure's Virtual Machine Scale Set (VMSS) feature. 170 | * Successfully configured scale-out and scale-in rules. 171 | * Successfully triggered the scale-out and scale-in process. 172 | 173 | ## Notes 174 | 175 | When used in a production environment, Panorama simplifies firewall configuration by automatically applying policy updates to all VMSS instances. 176 | 177 | ### [>> GO TO SCENARIO #5](../scenario5/README.md) 178 | -------------------------------------------------------------------------------- /scenario4/docs/scenario4-allowmgmntnsgpubip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario4/docs/scenario4-allowmgmntnsgpubip.png -------------------------------------------------------------------------------- /scenario4/docs/scenario4-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario4/docs/scenario4-architecture.png -------------------------------------------------------------------------------- /scenario4/docs/scenario4-scaling-cpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario4/docs/scenario4-scaling-cpu.png -------------------------------------------------------------------------------- /scenario4/docs/scenario4-scaling-cpu2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario4/docs/scenario4-scaling-cpu2.png -------------------------------------------------------------------------------- /scenario4/docs/scenario4-scaling-cpu3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario4/docs/scenario4-scaling-cpu3.png -------------------------------------------------------------------------------- /scenario4/docs/scenario4-scaling-newinstances.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario4/docs/scenario4-scaling-newinstances.png -------------------------------------------------------------------------------- /scenario4/docs/scenario4-scaling-newinstances2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario4/docs/scenario4-scaling-newinstances2.png -------------------------------------------------------------------------------- /scenario4/docs/scenario4-scaling-policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario4/docs/scenario4-scaling-policy.png -------------------------------------------------------------------------------- /scenario4/docs/scenario4-scaling-runhistory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario4/docs/scenario4-scaling-runhistory.png -------------------------------------------------------------------------------- /scenario4/docs/terraform_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsntg/microhack-azure-panfw/a54ba2257b42e057cf4d54eb286a444dc2c2ca42/scenario4/docs/terraform_apply.png -------------------------------------------------------------------------------- /scenario4/templates/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | .terraform.lock.hcl 8 | 9 | # Crash log files 10 | crash.log 11 | 12 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 13 | # .tfvars files are managed as part of configuration and so should be included in 14 | # version control. 15 | # 16 | # example.tfvars 17 | 18 | # Ignore override files as they are usually used to override resources locally and so 19 | # are not checked in 20 | override.tf 21 | override.tf.json 22 | *_override.tf 23 | *_override.tf.json 24 | 25 | # Include override files you do wish to add to version control using negated pattern 26 | # 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* -------------------------------------------------------------------------------- /scenario4/templates/files/init-cfg.txt: -------------------------------------------------------------------------------- 1 | hostname=pan-fw-ha-active-passive -------------------------------------------------------------------------------- /scenario4/templates/firewall.tf: -------------------------------------------------------------------------------- 1 | resource "random_integer" "id" { 2 | min = 100 3 | max = 999 4 | } 5 | 6 | resource "random_password" "password" { 7 | length = 16 8 | min_lower = 1 9 | min_numeric = 1 10 | min_special = 1 11 | min_upper = 1 12 | } 13 | 14 | data "http" "ipinfo" { 15 | url = "https://ifconfig.me" 16 | } 17 | 18 | resource "azurerm_public_ip" "fws_untrusted_pip" { 19 | name = "${var.firewall_vm_name}-elb-untrusted-pip" 20 | location = azurerm_resource_group.resource_group.location 21 | resource_group_name = azurerm_resource_group.resource_group.name 22 | allocation_method = "Static" 23 | sku = "Standard" 24 | } 25 | 26 | resource "local_file" "bootstrap" { 27 | 28 | content = templatefile("${path.module}/files/bootstrap.tpl", { 29 | next-hop-trusted = "${cidrhost(azurerm_subnet.subnet_pan_trusted.address_prefixes[0], 1)}" 30 | next-hop-untrusted = "${cidrhost(azurerm_subnet.subnet_pan_untrusted.address_prefixes[0], 1)}" 31 | }) 32 | filename = "${path.module}/files/bootstrap.xml" 33 | 34 | } 35 | 36 | module "bootstrap" { 37 | source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/bootstrap" 38 | version = "1.2.0" 39 | 40 | name = "paloaltobootstrap${random_integer.id.result}" 41 | location = azurerm_resource_group.resource_group.location 42 | resource_group_name = azurerm_resource_group.resource_group.name 43 | storage_share_name = "sharepaloaltobootstrap${random_integer.id.result}" 44 | storage_acl = false 45 | 46 | files = { 47 | "files/init-cfg.txt" = "config/init-cfg.txt" 48 | "files/bootstrap.xml" = "config/bootstrap.xml" 49 | } 50 | files_md5 = { 51 | "files/init-cfg.txt" = md5(file("files/init-cfg.txt")) 52 | "files/bootstrap.xml" = md5(local_file.bootstrap.content) 53 | } 54 | 55 | depends_on = [local_file.bootstrap] 56 | } 57 | 58 | resource "azurerm_linux_virtual_machine_scale_set" "example" { 59 | name = "panfw-vmss" 60 | location = azurerm_resource_group.resource_group.location 61 | resource_group_name = azurerm_resource_group.resource_group.name 62 | sku = "Standard_D3_v2" 63 | instances = 1 64 | //zones = var.avzones 65 | zone_balance = var.zone_balance 66 | provision_vm_agent = false 67 | identity { 68 | type = "SystemAssigned" 69 | } 70 | custom_data = base64encode(join(",", 71 | [ 72 | "storage-account=${module.bootstrap.storage_account.name}", 73 | "access-key=${module.bootstrap.storage_account.primary_access_key}", 74 | "file-share=${module.bootstrap.storage_share.name}", 75 | "share-directory=None" 76 | ] 77 | )) 78 | 79 | admin_username = var.username 80 | admin_password = coalesce(var.password, random_password.password.result) 81 | disable_password_authentication = false 82 | 83 | network_interface { 84 | name = "mgmt" 85 | primary = true 86 | 87 | ip_configuration { 88 | name = "mgmt" 89 | primary = true 90 | subnet_id = azurerm_subnet.subnet_pan_mgmt.id 91 | public_ip_address { 92 | name = "mgmt-pip" 93 | domain_name_label = "mgmt-${random_integer.id.result}" 94 | } 95 | } 96 | } 97 | 98 | network_interface { 99 | name = "trusted" 100 | enable_accelerated_networking = true 101 | enable_ip_forwarding = true 102 | 103 | ip_configuration { 104 | name = "trusted" 105 | primary = true 106 | subnet_id = azurerm_subnet.subnet_pan_trusted.id 107 | load_balancer_backend_address_pool_ids = [azurerm_lb_backend_address_pool.trusted_ilb_backendpool.id] 108 | } 109 | } 110 | 111 | network_interface { 112 | name = "untrusted" 113 | enable_accelerated_networking = true 114 | enable_ip_forwarding = true 115 | 116 | ip_configuration { 117 | name = "untrusted" 118 | primary = true 119 | subnet_id = azurerm_subnet.subnet_pan_untrusted.id 120 | load_balancer_backend_address_pool_ids = [azurerm_lb_backend_address_pool.untrusted_elb_backendpool.id] 121 | } 122 | } 123 | 124 | os_disk { 125 | caching = "ReadWrite" 126 | storage_account_type = "Standard_LRS" 127 | } 128 | 129 | source_image_reference { 130 | publisher = var.common_vmseries_publisher 131 | offer = var.common_vmseries_img_offer 132 | sku = var.common_vmseries_sku 133 | version = var.common_vmseries_version 134 | } 135 | plan { 136 | name = var.common_vmseries_sku 137 | publisher = var.common_vmseries_publisher 138 | product = var.common_vmseries_img_offer 139 | } 140 | 141 | //custom_data = filebase64("cloud-init.txt") 142 | 143 | computer_name_prefix = "vmss" 144 | upgrade_mode = "Manual" 145 | 146 | boot_diagnostics {} 147 | 148 | depends_on = [module.bootstrap] 149 | } 150 | 151 | # NSGs 152 | 153 | resource "azurerm_network_security_group" "fws-mgmt-nsg" { 154 | name = "${var.firewall_vm_name}-mgmt-nsg" 155 | location = azurerm_resource_group.resource_group.location 156 | resource_group_name = azurerm_resource_group.resource_group.name 157 | } 158 | 159 | resource "azurerm_network_security_rule" "network_security_rule_mgmt" { 160 | name = "mgmt-allow-inbound" 161 | resource_group_name = azurerm_resource_group.resource_group.name 162 | network_security_group_name = azurerm_network_security_group.fws-mgmt-nsg.name 163 | access = "Allow" 164 | direction = "Inbound" 165 | priority = 1000 166 | protocol = "Tcp" 167 | source_port_range = "*" 168 | source_address_prefixes = [coalesce(var.allow_inbound_mgmt_ips, data.http.ipinfo.response_body)] 169 | destination_address_prefix = "*" 170 | destination_port_range = "443" 171 | } 172 | 173 | resource "azurerm_subnet_network_security_group_association" "network_security_group_association_mgmt" { 174 | subnet_id = azurerm_subnet.subnet_pan_mgmt.id 175 | network_security_group_id = azurerm_network_security_group.fws-mgmt-nsg.id 176 | } 177 | 178 | resource "azurerm_network_security_group" "fws-untrusted-nsg" { 179 | name = "${var.firewall_vm_name}-untrusted-nsg" 180 | location = azurerm_resource_group.resource_group.location 181 | resource_group_name = azurerm_resource_group.resource_group.name 182 | } 183 | 184 | resource "azurerm_network_security_rule" "network_security_rule_untrusted" { 185 | name = "untrusted-allow-inbound" 186 | resource_group_name = azurerm_resource_group.resource_group.name 187 | network_security_group_name = azurerm_network_security_group.fws-untrusted-nsg.name 188 | access = "Allow" 189 | direction = "Inbound" 190 | priority = 1000 191 | protocol = "*" 192 | source_port_range = "*" 193 | source_address_prefix = "*" 194 | destination_address_prefix = "*" 195 | destination_port_range = "*" 196 | } 197 | 198 | resource "azurerm_subnet_network_security_group_association" "network_security_group_association_untrusted" { 199 | subnet_id = azurerm_subnet.subnet_pan_untrusted.id 200 | network_security_group_id = azurerm_network_security_group.fws-untrusted-nsg.id 201 | } -------------------------------------------------------------------------------- /scenario4/templates/lbs.tf: -------------------------------------------------------------------------------- 1 | # Internal Load Balancer - Trusted 2 | 3 | resource "azurerm_lb" "trusted_ilb" { 4 | resource_group_name = azurerm_resource_group.resource_group.name 5 | location = azurerm_resource_group.resource_group.location 6 | name = "panfw-trusted-ilb" 7 | sku = "Standard" 8 | frontend_ip_configuration { 9 | name = "panfw-trusted-ilb-ip" 10 | subnet_id = azurerm_subnet.subnet_pan_trusted.id 11 | } 12 | } 13 | 14 | # Get IP address of azurerm_lb.trusted_ilb frontend_ip_configuration 15 | 16 | resource "azurerm_lb_backend_address_pool" "trusted_ilb_backendpool" { 17 | name = "trusted_ilb_backend-pool" 18 | loadbalancer_id = azurerm_lb.trusted_ilb.id 19 | } 20 | 21 | resource "azurerm_lb_probe" "trusted_ilb_probe" { 22 | loadbalancer_id = azurerm_lb.trusted_ilb.id 23 | name = "trusted-ilb-probe" 24 | port = 443 25 | protocol = "Https" 26 | request_path = "/php/login.php" 27 | interval_in_seconds = 5 28 | number_of_probes = 2 29 | } 30 | 31 | resource "azurerm_lb_rule" "trusted_ilb_rules" { 32 | name = "all-ports" 33 | loadbalancer_id = azurerm_lb.trusted_ilb.id 34 | protocol = "All" 35 | frontend_port = 0 36 | backend_port = 0 37 | frontend_ip_configuration_name = "panfw-trusted-ilb-ip" 38 | idle_timeout_in_minutes = 5 39 | backend_address_pool_ids = [azurerm_lb_backend_address_pool.trusted_ilb_backendpool.id] 40 | probe_id = azurerm_lb_probe.trusted_ilb_probe.id 41 | } 42 | 43 | # External Load Balancer - Untrusted 44 | 45 | resource "azurerm_lb" "untrusted_elb" { 46 | resource_group_name = azurerm_resource_group.resource_group.name 47 | location = azurerm_resource_group.resource_group.location 48 | name = "panfw-untrusted-elb" 49 | sku = "Standard" 50 | frontend_ip_configuration { 51 | name = "panfw-untrusted-elb-ip" 52 | public_ip_address_id = azurerm_public_ip.fws_untrusted_pip.id 53 | } 54 | } 55 | 56 | resource "azurerm_lb_backend_address_pool" "untrusted_elb_backendpool" { 57 | name = "untrusted_elb_backend-pool" 58 | loadbalancer_id = azurerm_lb.untrusted_elb.id 59 | } 60 | 61 | resource "azurerm_lb_probe" "untrusted_elb_probe" { 62 | loadbalancer_id = azurerm_lb.untrusted_elb.id 63 | name = "untrusted-elb-probe" 64 | port = 22 65 | protocol = "Tcp" 66 | interval_in_seconds = 5 67 | number_of_probes = 2 68 | } 69 | 70 | resource "azurerm_lb_rule" "untrusted_elb_rules" { 71 | name = "TCP-22" 72 | loadbalancer_id = azurerm_lb.untrusted_elb.id 73 | protocol = "Tcp" 74 | frontend_port = 22 75 | backend_port = 22 76 | frontend_ip_configuration_name = "panfw-untrusted-elb-ip" 77 | idle_timeout_in_minutes = 5 78 | backend_address_pool_ids = [azurerm_lb_backend_address_pool.untrusted_elb_backendpool.id] 79 | probe_id = azurerm_lb_probe.untrusted_elb_probe.id 80 | disable_outbound_snat = true 81 | } 82 | 83 | resource "azurerm_lb_outbound_rule" "untrusted_elb_outbound_rules" { 84 | name = "untrusted-elb-outbound-rule" 85 | loadbalancer_id = azurerm_lb.untrusted_elb.id 86 | frontend_ip_configuration { 87 | name = "panfw-untrusted-elb-ip" 88 | } 89 | allocated_outbound_ports = 1000 90 | idle_timeout_in_minutes = 5 91 | protocol = "All" 92 | backend_address_pool_id = azurerm_lb_backend_address_pool.untrusted_elb_backendpool.id 93 | } 94 | -------------------------------------------------------------------------------- /scenario4/templates/main.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_marketplace_agreement" "paloaltonetworks" { 2 | publisher = "paloaltonetworks" 3 | offer = "vmseries-flex" 4 | plan = "byol" 5 | } 6 | 7 | resource "azurerm_marketplace_agreement" "paloaltonetworks" { 8 | count = data.azurerm_marketplace_agreement.paloaltonetworks.id == null ? 1 : 0 9 | publisher = "paloaltonetworks" 10 | offer = "vmseries-flex" 11 | plan = "byol" 12 | } 13 | 14 | resource "azurerm_resource_group" "resource_group" { 15 | name = var.resource_group_name 16 | location = var.location 17 | } -------------------------------------------------------------------------------- /scenario4/templates/natgw.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_public_ip" "panfw_untrusted_nat_gateway_pip" { 2 | name = "pafw-untrusted-natgw-pip" 3 | location = azurerm_resource_group.resource_group.location 4 | resource_group_name = azurerm_resource_group.resource_group.name 5 | allocation_method = "Static" 6 | sku = "Standard" 7 | } 8 | 9 | resource "azurerm_nat_gateway" "panfw_untrusted_nat_gateway" { 10 | name = "pafw-untrusted-natgw" 11 | location = azurerm_resource_group.resource_group.location 12 | resource_group_name = azurerm_resource_group.resource_group.name 13 | sku_name = "Standard" 14 | idle_timeout_in_minutes = 10 15 | //zones = ["1"] 16 | } 17 | 18 | resource "azurerm_nat_gateway_public_ip_association" "natgw_pip_association" { 19 | nat_gateway_id = azurerm_nat_gateway.panfw_untrusted_nat_gateway.id 20 | public_ip_address_id = azurerm_public_ip.panfw_untrusted_nat_gateway_pip.id 21 | } 22 | 23 | resource "azurerm_subnet_nat_gateway_association" "natgw_subnet_untrusted_association" { 24 | subnet_id = azurerm_subnet.subnet_pan_untrusted.id 25 | nat_gateway_id = azurerm_nat_gateway.panfw_untrusted_nat_gateway.id 26 | } -------------------------------------------------------------------------------- /scenario4/templates/network.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_virtual_network" "virtual_network_hub" { 2 | name = var.vnet_hub_name 3 | address_space = [var.vnet_hub_address_space] 4 | location = azurerm_resource_group.resource_group.location 5 | resource_group_name = azurerm_resource_group.resource_group.name 6 | } 7 | 8 | resource "azurerm_subnet" "subnet_pan_mgmt" { 9 | name = "pan-mgmt-subnet" 10 | resource_group_name = azurerm_resource_group.resource_group.name 11 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 12 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 0)] 13 | } 14 | 15 | resource "azurerm_subnet" "subnet_pan_untrusted" { 16 | name = "pan-untrusted-subnet" 17 | resource_group_name = azurerm_resource_group.resource_group.name 18 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 19 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 1)] 20 | } 21 | 22 | resource "azurerm_subnet" "subnet_pan_trusted" { 23 | name = "pan-trusted-subnet" 24 | resource_group_name = azurerm_resource_group.resource_group.name 25 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 26 | address_prefixes = [cidrsubnet(var.vnet_hub_address_space, 4, 2)] 27 | } 28 | 29 | resource "azurerm_virtual_network" "virtual_network_spoke01" { 30 | name = var.vnet_spoke01_name 31 | address_space = [var.vnet_spoke01_address_space] 32 | location = azurerm_resource_group.resource_group.location 33 | resource_group_name = azurerm_resource_group.resource_group.name 34 | } 35 | 36 | resource "azurerm_subnet" "subnet_spoke01_default" { 37 | name = "snet-default" 38 | resource_group_name = azurerm_resource_group.resource_group.name 39 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke01.name 40 | address_prefixes = [cidrsubnet(var.vnet_spoke01_address_space, 4, 0)] 41 | } 42 | 43 | resource "azurerm_virtual_network_peering" "peering_hub_to_spoke01" { 44 | name = "hub-to-spoke01" 45 | resource_group_name = azurerm_resource_group.resource_group.name 46 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 47 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_spoke01.id 48 | allow_virtual_network_access = true 49 | allow_forwarded_traffic = true 50 | } 51 | 52 | resource "azurerm_virtual_network_peering" "peering_spoke01_to_hub" { 53 | name = "spoke01-to-hub" 54 | resource_group_name = azurerm_resource_group.resource_group.name 55 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke01.name 56 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_hub.id 57 | allow_virtual_network_access = true 58 | allow_forwarded_traffic = true 59 | } 60 | 61 | resource "azurerm_virtual_network" "virtual_network_spoke02" { 62 | name = var.vnet_spoke02_name 63 | address_space = [var.vnet_spoke02_address_space] 64 | location = azurerm_resource_group.resource_group.location 65 | resource_group_name = azurerm_resource_group.resource_group.name 66 | } 67 | 68 | resource "azurerm_subnet" "subnet_spoke02_default" { 69 | name = "snet-default" 70 | resource_group_name = azurerm_resource_group.resource_group.name 71 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke02.name 72 | address_prefixes = [cidrsubnet(var.vnet_spoke02_address_space, 4, 0)] 73 | } 74 | 75 | resource "azurerm_virtual_network_peering" "peering_hub_to_spoke02" { 76 | name = "hub-to-spoke02" 77 | resource_group_name = azurerm_resource_group.resource_group.name 78 | virtual_network_name = azurerm_virtual_network.virtual_network_hub.name 79 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_spoke02.id 80 | allow_virtual_network_access = true 81 | allow_forwarded_traffic = true 82 | } 83 | 84 | resource "azurerm_virtual_network_peering" "peering_spoke02_to_hub" { 85 | name = "spoke02-to-hub" 86 | resource_group_name = azurerm_resource_group.resource_group.name 87 | virtual_network_name = azurerm_virtual_network.virtual_network_spoke02.name 88 | remote_virtual_network_id = azurerm_virtual_network.virtual_network_hub.id 89 | allow_virtual_network_access = true 90 | allow_forwarded_traffic = true 91 | } 92 | 93 | resource "azurerm_route_table" "route_table_spoke01" { 94 | name = "spoke01-rt" 95 | location = azurerm_resource_group.resource_group.location 96 | resource_group_name = azurerm_resource_group.resource_group.name 97 | disable_bgp_route_propagation = true 98 | } 99 | 100 | resource "azurerm_route" "route_spoke01_default" { 101 | name = "spoke01-default-route" 102 | resource_group_name = azurerm_resource_group.resource_group.name 103 | route_table_name = azurerm_route_table.route_table_spoke01.name 104 | address_prefix = "0.0.0.0/0" 105 | next_hop_type = "VirtualAppliance" 106 | next_hop_in_ip_address = azurerm_lb.trusted_ilb.private_ip_address 107 | } 108 | 109 | resource "azurerm_subnet_route_table_association" "route_table_association_spoke01" { 110 | subnet_id = azurerm_subnet.subnet_spoke01_default.id 111 | route_table_id = azurerm_route_table.route_table_spoke01.id 112 | } 113 | 114 | 115 | resource "azurerm_route_table" "route_table_spoke02" { 116 | name = "spoke02-rt" 117 | location = azurerm_resource_group.resource_group.location 118 | resource_group_name = azurerm_resource_group.resource_group.name 119 | disable_bgp_route_propagation = true 120 | } 121 | 122 | resource "azurerm_route" "route_spoke02_default" { 123 | name = "spoke02-default-route" 124 | resource_group_name = azurerm_resource_group.resource_group.name 125 | route_table_name = azurerm_route_table.route_table_spoke02.name 126 | address_prefix = "0.0.0.0/0" 127 | next_hop_type = "VirtualAppliance" 128 | next_hop_in_ip_address = azurerm_lb.trusted_ilb.private_ip_address 129 | } 130 | 131 | resource "azurerm_subnet_route_table_association" "route_table_association_spoke02" { 132 | subnet_id = azurerm_subnet.subnet_spoke02_default.id 133 | route_table_id = azurerm_route_table.route_table_spoke02.id 134 | } -------------------------------------------------------------------------------- /scenario4/templates/output.tf: -------------------------------------------------------------------------------- 1 | /*output "paloalto_vmseries_01_dns" { 2 | value = "https://${azurerm_public_ip.fw01_mgmt_pip.fqdn}" 3 | } 4 | 5 | output "paloalto_vmseries_02_dns" { 6 | value = "https://${azurerm_public_ip.fw02_mgmt_pip.fqdn}" 7 | }*/ 8 | 9 | output "paloalto_username" { 10 | value = var.username 11 | } 12 | 13 | # Use "terraform output paloalto_password" to get the password after terraform apply 14 | output "paloalto_password" { 15 | value = coalesce(var.password, random_password.password.result) 16 | sensitive = true 17 | } -------------------------------------------------------------------------------- /scenario4/templates/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = "=3.76" 6 | } 7 | 8 | http = { 9 | source = "hashicorp/http" 10 | version = "3.1.0" 11 | } 12 | } 13 | } 14 | 15 | provider "azurerm" { 16 | features { 17 | resource_group { 18 | prevent_deletion_if_contains_resources = false 19 | } 20 | virtual_machine_scale_set { 21 | roll_instances_when_required = false 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /scenario4/templates/variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | type = string 3 | default = "West Europe" 4 | } 5 | 6 | variable "resource_group_name" { 7 | type = string 8 | default = "rg-panfw-scenario4" 9 | } 10 | 11 | variable "vnet_hub_name" { 12 | type = string 13 | default = "hub-vnet" 14 | } 15 | 16 | variable "vnet_hub_address_space" { 17 | type = string 18 | default = "10.0.0.0/24" 19 | } 20 | 21 | variable "vnet_spoke01_name" { 22 | type = string 23 | default = "spoke01-vnet" 24 | } 25 | 26 | variable "vnet_spoke01_address_space" { 27 | type = string 28 | default = "10.0.1.0/24" 29 | } 30 | 31 | variable "vnet_spoke02_name" { 32 | type = string 33 | default = "spoke02-vnet" 34 | } 35 | 36 | variable "vnet_spoke02_address_space" { 37 | type = string 38 | default = "10.0.2.0/24" 39 | } 40 | 41 | variable "firewall_vm_name" { 42 | type = string 43 | default = "panfw-vm" 44 | } 45 | 46 | variable "allow_inbound_mgmt_ips" { 47 | default = "" 48 | type = string 49 | } 50 | 51 | variable "common_vmseries_publisher" { 52 | description = "VM-Series publisher - list available with `az vm image list -o table --all --publisher paloaltonetworks`" 53 | default = "paloaltonetworks" 54 | type = string 55 | } 56 | 57 | variable "common_vmseries_img_offer" { 58 | description = "VM-Series offer - list available with `az vm image list -o table --all --publisher paloaltonetworks`" 59 | default = "vmseries-flex" 60 | type = string 61 | } 62 | 63 | variable "common_vmseries_sku" { 64 | description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" 65 | default = "byol" 66 | type = string 67 | } 68 | 69 | variable "common_vmseries_version" { 70 | description = "VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`" 71 | default = "latest" 72 | type = string 73 | } 74 | 75 | variable "common_vmseries_vm_size" { 76 | description = "Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported." 77 | default = "Standard_A4_v2" 78 | type = string 79 | } 80 | 81 | variable "username" { 82 | description = "Initial administrative username to use for all systems." 83 | default = "panadmin" 84 | type = string 85 | } 86 | 87 | variable "password" { 88 | description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." 89 | default = "Microsoft=1Microsoft=1" 90 | type = string 91 | } 92 | 93 | variable "avzones" { 94 | type = list(string) 95 | default = ["1", "2", "3"] 96 | } 97 | 98 | variable "zone_balance" { 99 | type = bool 100 | default = false 101 | } 102 | 103 | variable "vm_size" { 104 | type = string 105 | //default = "Standard_DS1_v2" 106 | default = "Standard_D2s_v5" 107 | description = "VM Size" 108 | } 109 | 110 | variable "vm_os_publisher" { 111 | type = string 112 | default = "canonical" 113 | description = "VM OS Publisher" 114 | } 115 | 116 | variable "vm_os_offer" { 117 | type = string 118 | default = "0001-com-ubuntu-server-jammy" 119 | description = "VM OS Offer" 120 | } 121 | 122 | variable "vm_os_sku" { 123 | type = string 124 | default = "22_04-lts-gen2" 125 | description = "VM OS Sku" 126 | } 127 | 128 | variable "vm_os_version" { 129 | type = string 130 | default = "latest" 131 | description = "VM OS Version" 132 | } 133 | 134 | locals { 135 | custom_script_spoke1 = <