├── .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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
85 |
86 | * Click 'OK' and Commit changes
87 | * Check *ethernet1/3* is configure and up:
88 |
89 | 
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 | 
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 | 
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 | 
142 |
143 | * Observe the HA status on both appliances:
144 |
145 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
238 | * Outbound IP
239 | * Public IP addresses: (New) panfw-untrusted-natgw-pip01
240 |
241 | 
242 | * Subnet:
243 | * VNet: hub-vnet
244 | * Subnet: pan-untrusted-subnet
245 |
246 | 
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 | 
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 | 
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 | 
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 | 
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 | 
142 |
143 | * Wait ~10 minutes and observe the creation on new Instances in 'Instances' tab:
144 |
145 | 
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 | 
150 |
151 | * Display again the CPU (average) of the VMSS:
152 |
153 | 
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 | 
162 |
163 | This scale in was trigger by the scale-in policy configured && triggered before:
164 |
165 | 
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 = <