TTechclick ⚡ XP 0% All lessons
Ansible · Security · Firewall AutomationInteractive · L1 / L2 / L3

Ansible for Firewall Automation: — Palo Alto and Fortinet Rules as Code

A change ticket says 'allow the new payment app from the DMZ'. One engineer clicks through the Palo Alto GUI, another clicks through FortiGate, and three months later nobody remembers why rule 47 exists. This lesson turns both firewalls into version-controlled code — one playbook, one inventory, reviewed in a PR and rolled out on purpose.

📅 2026-06-11 · ⏱ 13 min · 3 live demos · 4 infographics · 🏷 10-Q assessment + AI Tutor inline

⚡ Quick Answer

Automate Palo Alto and Fortinet firewall rules with Ansible for L1/L2 engineers and RHCE/PCNSE prep: panos vs fortios collections, the PAN-OS commit gotcha, FortiOS object-before-policy order, and safe git-gated rollout.

🎯 By the end you will be able to

Read as:

Pick where you want to start

1

Firewall-as-code

Why GUI clicks and drift break audits — and the fix.

2

Palo Alto + panos

Objects, security rules, and the commit gotcha.

3

Fortinet + fortios

REST, VDOMs, tokens, object-before-policy order.

4

Safe rollout

Git, --check, gated commit, both vendors from one run.

🧠 Warm-up — 3 questions, no score

Just notice which ones make you pause. We answer all three inside the lesson.

1. You run an Ansible playbook that creates a Palo Alto security rule and it reports 'changed'. Is the rule live and blocking/allowing traffic yet?

Answered in Firewall-as-code.

2. On FortiGate, you write a policy that references an address object 'web-app' that you haven't created yet. What happens?

Answered in Fortinet + fortios.

3. Which Ansible connection type do the panos and fortios firewall collections use to talk to the box?

Answered in Palo Alto + panos.

Most engineers think…

Most engineers think automating a firewall just means 'SSH into the box with Ansible and paste the CLI commands' — so they reach for the network_cli connection like they would for a Cisco switch.

Wrong — and it will fail on day one. The Palo Alto and Fortinet firewall collections do not drive the CLI. paloaltonetworks.panos talks to PAN-OS over its XML API (via a provider / httpapi connection), and fortinet.fortios talks to FortiGate over its REST API (an httpapi connection with an access_token). You manage objects and policy declaratively, and on Palo Alto nothing is live until you commit — a step that has no equivalent on a switch and trips up almost everyone the first time.

① Firewall-as-code — killing drift, GUI clicks and the mystery rules

Meet Sneha, an L2 engineer at Flipkart. A change ticket lands: "Allow the new payment service (203.0.113.40) to reach the app on tcp/8443." She opens the Palo Alto GUI, clicks Policies → Security, fills a form, clicks Commit. Same week, a teammate does the same change on the FortiGate by hand. Six months later an auditor asks "why does rule 47 exist and who approved it?" — and nobody knows. That gap is the disease this lesson cures.

Three things go wrong with click-by-hand firewalls. First, configuration drift: the running rulebase no longer matches any document. Second, no review: a fat-fingered any-any rule goes live with nobody checking. Third, no rollback: undoing a bad change means remembering what it used to be. Multiply that by two vendors with different GUIs and you have the classic outage waiting to happen.

Rules as code flips it. The firewall's objects and policies are described in an Ansible playbook kept in git. A change is now a pull request a colleague reviews; a wrong change is a git revert; and 'why does this rule exist?' is answered by the commit message. The firewall stops being a black box you poke and becomes a file you reason about.

👉 So far: hand-clicking two firewalls causes drift, no review, no rollback. Next: how Ansible actually reaches each box — and why it is NOT over SSH.

Here's the part that surprises people coming from switch automation. Ansible does not SSH into these firewalls and type CLI. paloaltonetworks.panos drives PAN-OS over its XML API using a provider dict (or an httpapi connection). fortinet.fortios drives FortiGate over its REST API using the httpapi connection. So the connection is httpapi or local — never network_cli. The modules speak the firewall's native API, with structured idempotency baked in.

Figure 1 — Hand-clicked firewalls drift; rules-as-code makes every change reviewed and reversible
Clicking two firewall GUIs by hand causes drift and mystery rules; one reviewed playbook makes both vendors auditable code A two-column comparison. Left, the manual world in red: an engineer clicks the Palo Alto GUI and another clicks the FortiGate GUI, with no review, no diff and no rollback, producing configuration drift and an undocumented rule 47. Right, the rules-as-code world in green: a single git repository holds one playbook and one inventory, a pull request is reviewed by a second engineer, the play is dry-run with check mode, and only then does Ansible drive the PAN-OS XML API and the FortiOS REST API to apply the same intent to both firewalls. Click-by-hand vs rules-as-code — same change, two worlds Manual GUI clicks (drift) Palo Alto GUI FortiGate GUI ✗ two people, two consoles, no review ✗ no diff, no approval, no audit trail ✗ rollback = 'remember what it was' ✗ config drifts from any document 6 months later: "why does rule 47 exist?"nobody knows · who approved it? · undocumented Rules as code (git + Ansible) git repo · 1 playbook · 1 inventory ✓ change = pull request, peer reviewed ✓ --check dry-run previews the impact ✓ full history → audit + git revert rollback panos → PAN-OSXML API fortios → FortiGateREST API one intent → both vendors, reviewed & reversible untrusted/manual driftautomation pathpolicy/decisionkey insightreviewed/allowed
Left (red) = two GUIs, two people, no record → drift and mystery rule 47. Right (green) = one git repo, a PR review, --check preview, then Ansible drives PAN-OS XML API and FortiOS REST API.

The four wins of rules-as-code, one tap each

Tap each card — these are the lines you'll repeat to a manager who asks 'why bother automating the firewall?'

📜
Auditable history
tap to flip

Every change is a git commit with author, time and diff. So: 'who changed rule 47 and why' is answered, not guessed.

👀
Peer review
tap to flip

A change is a PR a second engineer approves before merge. So: a fat-fingered any-any rule gets caught off the firewall.

↩️
Easy rollback
tap to flip

Undo is git revert + re-run, not 'remember the old value'. So: a bad rule is gone in minutes, cleanly.

🔁
Two vendors, one intent
tap to flip

One inventory drives Palo Alto and Fortinet from the same source of truth. So: no more divergent hand-config per box.

Daily-life analogy — the society gate-pass register

Manual firewall clicks are like a security guard who lets visitors in on a nod and never writes it down — a month later nobody knows who entered or why. Rules-as-code is the society gate-pass register: every entry is logged with who, when and approved-by, the watchman follows a written list, and if a wrong name got in you can see exactly which entry to strike off. The firewall rulebase is your building; git is the register.

Pause & Predict

Predict: you point Ansible at a Palo Alto firewall using the network_cli connection (the one you'd use for a Cisco switch). What goes wrong? Type your guess.

Answer: It fails. The paloaltonetworks.panos modules don't drive the CLI — they call the PAN-OS XML API through a provider dict or an httpapi connection. With network_cli there's no API session for the module to use, so tasks error out on connection/auth. The fix is to use the provider params (or set connection: httpapi for the API plugin) — never network_cli for these firewall collections.
Quick check · Q1 of 10

Aditya at Wipro asks: "What's the single biggest reason to move firewall changes from the GUI into Ansible playbooks in git?"

Correct: b. The point of rules-as-code is governance: a change is a reviewed PR with a full git history you can audit and revert — killing drift and 'mystery rule 47'. It has nothing to do with forwarding speed, you still need rules (you're just defining them differently), and these collections deliberately don't SSH to the CLI at all.

② Palo Alto with panos — objects, security rules, and the commit step

Let's automate the Flipkart change properly on Palo Alto. Two ingredients: objects (named things like an address) and a security rule that references them. With panos_address_object you create the address partner-pay-host = 203.0.113.40, and with panos_security_rule you write the allow rule. The object comes first so the rule can point at it by name.

Connection is via the provider dict — ip_address, username, and a password or api_key (the modern path is an httpapi connection, but provider is what you'll see most). Key rule fields map straight to the GUI you already know: source_zone / destination_zone (e.g. untrustdmz), source_ip / destination_ip, application (App-ID, e.g. web-browsing or ssl), service (defaults to application-default), action (allow/deny/drop), tag_name, and crucially location (top/bottom/before/after) which controls where in the rulebase it lands.

👉 So far: object first, then a security rule, fields mirror the GUI. Next: the step everyone forgets — the commit.

Now the gotcha that catches every newcomer. Your panos_security_rule task writes to the candidate config, not the running config. Nothing changes for users until a commit copies candidate to running. So a panos playbook is incomplete without panos_commit_firewall at the end. The inline commit: true parameter on the rule module is deprecated — use the dedicated commit module instead.

If you're driving Panorama instead of a single firewall, two more things change. You target a device_group (and optionally a template for network config), and committing is two steps: panos_commit_panorama commits Panorama, then panos_commit_push pushes it down to the firewalls. Miss the push and your rule lives on Panorama but never reaches the box enforcing traffic.

Figure 2 — On Palo Alto, a rule isn't live until commit copies candidate to running
A panos playbook writes object and rule into PAN-OS candidate config; only a commit makes them enforce, and Panorama also needs a push A left-to-right flow. The Ansible control node runs three tasks: panos_address_object then panos_security_rule write into the PAN-OS candidate configuration, shown amber to mean not yet enforcing. A third task, panos_commit_firewall, commits candidate to the running configuration, shown green to mean now enforcing traffic. A separate branch shows the Panorama path where panos_commit_panorama then panos_commit_push are needed to reach managed firewalls. A break note warns that forgetting the commit leaves the rule in candidate and traffic unchanged. PAN-OS: candidate → (commit) → running Ansible controlpaloaltonetworks.panosXML API · provider Candidate config (not enforcing) 1· panos_address_objectpartner-pay-host = 203.0.113.40 2· panos_security_ruleuntrust→dmz · ssl · allow · location: bottom Running confignow enforcing traffic 3· panos_commit_firewallcandidate → running commit Panorama path: device_group → commit then PUSHpanos_commit_panorama → panos_commit_push → reaches managed firewalls Key insightno commit = rule invisible to traffic Forget the commit → play says 'changed', users still blocked. Inline commit: true is deprecated; use the commit module. forgot / blockedautomation pathcandidate/decisionkey insightrunning/enforcing
Follow the arrows: panos tasks write the OBJECT then the RULE into candidate (amber). Users are unaffected until panos_commit_firewall copies candidate → running (green). On Panorama add a push to the firewalls.
ansible-playbook — Palo Alto: object first, security rule, then COMMIT (the step everyone forgets)
- name: Flipkart payment allow on PAN-OS
  hosts: paloalto
  connection: local
  gather_facts: false
  vars:
    provider:
      ip_address: "{{ pan_ip }}"
      username: "{{ pan_user }}"
      api_key: "{{ pan_api_key }}"     # from vault — next lesson
  tasks:
    - name: 1) Address object FIRST
      paloaltonetworks.panos.panos_address_object:
        provider: "{{ provider }}"
        name: "partner-pay-host"
        value: "203.0.113.40"
        description: "Partner payment service"

    - name: 2) Security rule that references it
      paloaltonetworks.panos.panos_security_rule:
        provider: "{{ provider }}"
        rule_name: "allow-partner-pay"
        source_zone: ["untrust"]
        destination_zone: ["dmz"]
        source_ip: ["partner-pay-host"]
        destination_ip: ["app-pay-server"]
        application: ["ssl"]
        action: "allow"
        tag_name: ["change-CHG10422"]
        location: "bottom"

    - name: 3) COMMIT — candidate -> running (or it never enforces)
      paloaltonetworks.panos.panos_commit_firewall:
        provider: "{{ provider }}"
Expected output
PLAY [Flipkart payment allow on PAN-OS] ****************************
TASK [1) Address object FIRST] ************************ changed: [pa-fw-01]
TASK [2) Security rule that references it] *********** changed: [pa-fw-01]
TASK [3) COMMIT - candidate -> running] ************** changed: [pa-fw-01]
PLAY RECAP ********** pa-fw-01 : ok=3 changed=3 unreachable=0 failed=0
🖥️ The rule your playbook wrote, seen in the GUI — Palo Alto Web UI → Policies → Security → (your rule), with the pending-changes/Commit button lit. (Recreated for clarity — your console matches this.)
PA-VM Web UI · Policies → Security
1
Name
allow-partner-pay
2
Source Zone
untrust
3
Destination Zone
dmz
Application
ssl
Action
Allow
4
Status
uncommitted change pending
Commit
Common mistake — 'the play said changed but my rule isn't working'

Symptom: ansible-playbook reports changed for your panos_security_rule task, but traffic is still blocked and the GUI shows an uncommitted change. Cause: you wrote candidate config but never committed it to running. Fix: add a panos_commit_firewall task (single firewall) or panos_commit_panorama + panos_commit_push (Panorama). Real-world twist from the field: a known bug after upgrading Panorama to 11.x made panos_commit_panorama report success while the config stayed on candidate (it worked on 10.2.8) — so always verify the running config, not just the task result.

Quick check · Q2 of 10

Neha at HCL automates a Palo Alto allow rule. The play finishes with ok=2 changed=2 and no errors, but users still can't reach the app. The GUI shows 'changes pending'. What's missing from her playbook?

Correct: c. 'Changes pending' is PAN-OS telling you the rule is in candidate, not running. Adding panos_commit_firewall commits it so it enforces. PAN-OS doesn't need a reboot, the collection is correct for a Palo Alto box, and these modules don't use network_cli.

Pause & Predict

Predict: on Panorama you run panos_security_rule into a device_group and panos_commit_panorama, but skip panos_commit_push. Will the managed firewalls enforce the rule? Type your guess.

Answer: No. panos_commit_panorama only commits Panorama's own candidate config; the rule now exists in Panorama's committed config but hasn't been pushed to the firewalls that actually enforce traffic. You also need panos_commit_push to push the device-group config down to the managed firewalls. Commit-then-push is two distinct steps on Panorama — miss the push and the box keeps enforcing its old rulebase.

③ Fortinet with fortios — REST, VDOMs, tokens and object order

Same change, now on the FortiGate. The fortinet.fortios collection talks to the box over its REST API using the httpapi connection plugin (set ansible_connection: httpapi). The old fortiosapi Python library path is deprecated — httpapi is the supported way now. So, like Palo Alto, you're driving an API, not SSH-ing to the CLI.

Auth is a REST API token. You generate it on the box under System → Administrators → Create New → REST API Admin, then pass it to tasks as access_token (kept in Vault, of course). Every fortios task also names a vdom — default root — so your objects land in the right virtual firewall. Put an object in the wrong vdom and the policy in another vdom can't see it.

👉 So far: REST over httpapi, an access_token, and a vdom per task. Next: the ordering trap — objects before the policy that uses them.

The FortiGate version of the object-first rule is strict and bites people constantly. A fortios_firewall_policy references its source/destination by the names of fortios_firewall_address objects. If the policy task runs before those address objects exist, FortiOS rejects it with entry not found in datasource. Ansible runs tasks top-down, so the fix is just ordering: address tasks above the policy task.

One more FortiGate-specific wrinkle: policy position and idempotency. A policy has a numeric policyid and an order in the list (FortiGate evaluates top-down, first match wins). Creating/updating a policy by id is idempotent — run it twice, the second run is ok not changed. But moving a policy's position is a separate action: move (before/after) operation; if you rely on creation order alone, re-running may not re-assert position, so be explicit when order matters to avoid rule shadowing.

Figure 3 — FortiGate: the address object must exist before the policy that names it — or the task errors
On FortiGate a policy resolves source and destination by object name, so the firewall address objects must be created before the policy or the task fails Two rows comparing task order on FortiGate over the fortios httpapi REST connection. The top row, correct and green: task one creates a fortios_firewall_address object partner-pay-host, task two creates pay-app, task three creates the fortios_firewall_policy that references both by name and succeeds. The bottom row, wrong and red: the policy task runs first, references names that do not exist yet, and FortiOS returns entry not found in datasource. Every task carries a vdom set to root and an access_token for authentication. FortiGate task order: object-before-policy (REST · httpapi · vdom: root) ✓ Correct order — objects first 1· fortios_firewall_addresspartner-pay-host 203.0.113.40 2· fortios_firewall_addresspay-app 172.16.40.9/32 3· fortios_firewall_policysrcaddr/dstaddr resolve → accept policy liveno commit needed ✗ Wrong order — policy first 1· fortios_firewall_policysrcaddr: partner-pay-host (missing!) FortiOS errorentry not found in datasource Position matters too: top-down, first match wins → a broad rule above a tight one = shadowing wrong order / errortask flowposition/decisionkey insightcorrect / live
Top row (green, correct): address objects created first, then the policy resolves their names. Bottom row (red, wrong): policy first → FortiOS returns 'entry not found in datasource'. Note the vdom and access_token on every task.

▶ Watch one FortiGate change land over REST

Sneha's same payment-allow change, now on the FortiGate. Follow it from the Ansible control node through the httpapi REST session into vdom root. Press Play for the healthy path, then Break it to see the failure.

① Connectcontrol node opens httpapi session to fortigate, auth via access_token, vdom=root
② Address objfortios_firewall_address creates partner-pay-host = 203.0.113.40 in vdom root
③ Policyfortios_firewall_policy id 12: srcaddr resolves the named object → action accept
④ LiveREST write applied immediately — no commit step; re-run reports ok (idempotent)
Press Play to step through the healthy path. Then press Break it.
ansible-playbook — FortiGate: httpapi + access_token, addresses first, then the policy (vdom root)
- name: Flipkart payment allow on FortiGate
  hosts: fortigate
  connection: httpapi
  gather_facts: false
  vars:
    vdom: "root"
    ansible_httpapi_use_ssl: true
    ansible_httpapi_validate_certs: false
  tasks:
    - name: 1) Address objects FIRST
      fortinet.fortios.fortios_firewall_address:
        access_token: "{{ forti_token }}"   # from vault
        vdom: "{{ vdom }}"
        state: present
        firewall_address:
          name: "partner-pay-host"
          type: ipmask
          subnet: "203.0.113.40 255.255.255.255"

    - name: 2) Policy that references the object by name
      fortinet.fortios.fortios_firewall_policy:
        access_token: "{{ forti_token }}"
        vdom: "{{ vdom }}"
        state: present
        firewall_policy:
          policyid: 12
          name: "allow-partner-pay"
          srcintf: [{ name: "port2" }]
          dstintf: [{ name: "port1" }]
          srcaddr: [{ name: "partner-pay-host" }]
          dstaddr: [{ name: "pay-app" }]
          service: [{ name: "HTTPS" }]
          schedule: "always"
          action: "accept"
          status: "enable"
Expected output
PLAY [Flipkart payment allow on FortiGate] ************************
TASK [1) Address objects FIRST] ********************** changed: [fg-01]
TASK [2) Policy that references the object by name] ** changed: [fg-01]
PLAY RECAP ********** fg-01 : ok=2 changed=2 unreachable=0 failed=0
# re-run -> ok=2 changed=0  (idempotent: state already matches)
Prove the FortiGate policy is real, not just 'changed'

After the play, confirm the object and policy exist in the right vdom rather than trusting the task result. Quick check on the box: config vdomedit rootshow firewall address partner-pay-host and show firewall policy 12 should both return your config. Better still, re-run the playbook with --check: if everything reports ok (no changed), reality already matches your code — that's idempotency proving the change stuck.

Arjun at ICICI faces this

Arjun, an L1 engineer, runs the FortiGate firewall playbook and the policy task fails immediately with 'entry not found in datasource' on srcaddr. The address task and the policy task are both in the file.

Likely cause

Task order. The policy task is listed ABOVE the fortios_firewall_address tasks, so Ansible runs the policy first — and it references object names that FortiOS hasn't created yet. (Or the objects were created in a different vdom than the policy.)

Diagnosis

He reads the play top-to-bottom like Ansible does, confirms the policy is above the address objects, and checks that both tasks use the same vdom (root).

playbook tasks: list fortios_firewall_address ABOVE fortios_firewall_policy; verify vdom matches on both (System → VDOM on the GUI)
Fix

Reorder so the two fortios_firewall_address tasks come first, then the fortios_firewall_policy, all in vdom root. Re-run the play.

Verify

Play now reports changed for the address objects and the policy; show firewall policy 12 on the box lists srcaddr partner-pay-host, and traffic from 203.0.113.40 reaches the app.

Quick check · Q3 of 10

Meera at Airtel writes a FortiGate policy referencing srcaddr 'branch-net' and runs it. It fails with 'entry not found in datasource'. The play has both an address task and the policy task. Most likely cause?

Correct: d. A FortiGate policy resolves srcaddr/dstaddr by object name, so the address object must be created first and in the same vdom — wrong order (or wrong vdom) gives 'entry not found in datasource'. FortiGate config has no commit step, the panos collection is for Palo Alto, and a token region mismatch would give an auth/connection error, not a datasource error.

Pause & Predict

Predict: you run the FortiGate playbook twice in a row without changing anything. What does the PLAY RECAP show on the second run, and what does that tell you? Type your guess.

Answer: The second run shows changed=0 (everything ok). That's idempotency: the modules describe a desired state, the first run made it real, and the second run finds reality already matches — so nothing is rewritten. It's the proof that your rules-as-code is safe to re-run on a schedule without churning the firewall, and a quick way to confirm a change actually stuck.

④ Safe rollout — git, --check, gated commit, both vendors from one run

You can write rules in code now. The discipline that makes it safe is the rollout flow: playbooks live in git, every change is a pull request a colleague reviews, the play is dry-run with --check, and the commit/push is a deliberate gated step — never an accident. Manual GUI clicking had none of these gates; this is where the payoff is.

Check mode is your preview. ansible-playbook site.yml --check --diff reports which rules would be created or changed, and the diff shows before/after — all without touching the firewall. On Palo Alto you get a second safety net for free: even a real run leaves edits in candidate config until you run the commit task, so you can stage changes and commit only after a human says go. That commit becomes your gate.

👉 So far: git PR + --check + a gated commit. Next: one inventory pushing the same rule to BOTH vendors, then the gotchas to dodge.

Now the headline trick: one inventory, both vendors. You group your boxes — a paloalto group and a fortigate group — and keep the intent once in group_vars (allow 203.0.113.40 → the payment app on HTTPS). One play targets the paloalto group with panos modules (+ commit); another targets the fortigate group with fortios modules. The same business change is translated into each vendor's object + rule syntax from a single source of truth.

Terminal — the gated rollout: branch, dry-run, review, then run for real (both vendors)
arjun@netauto:~/firewall-iac$ git checkout -b chg10422-allow-pay
arjun@netauto:~/firewall-iac$ ansible-playbook -i inventory.ini site.yml --check --diff
arjun@netauto:~/firewall-iac$ git commit -am "CHG10422 allow 203.0.113.40 -> pay-app (HTTPS)"
arjun@netauto:~/firewall-iac$ git push origin chg10422-allow-pay   # open PR, get review
# --- after a teammate approves the PR ---
arjun@netauto:~/firewall-iac$ ansible-playbook -i inventory.ini site.yml
Expected output
PLAY [Palo Alto] ** changed: address-object, security-rule, COMMIT (candidate->running)
PLAY [FortiGate] ** changed: firewall_address x2, firewall_policy (REST, live)
PLAY RECAP **********
pa-fw-01 : ok=3  changed=3  failed=0
fg-01    : ok=3  changed=3  failed=0
Figure 4 — One reviewed change flows to both vendors — and the only deliberate live moment is the commit
A safe firewall-as-code rollout: git branch and PR review, then a check-mode preview, then one run that applies the change to Palo Alto and Fortinet from a single inventory A left-to-right pipeline. Stage one, an engineer creates a git branch and opens a pull request reviewed by a second engineer. Stage two, the play is run with check mode to preview which rules would change. Stage three, after approval the branch merges. Stage four, a single ansible-playbook run uses one inventory to drive both vendors: the Palo Alto group via paloaltonetworks.panos ending in a deliberate commit gate, and the FortiGate group via fortinet.fortios applied live over REST. Amber marks the commit decision gate, green marks the enforced result. Gated rollout: review → preview → merge → one run, both vendors 1· branch + PRpeer reviews the diff 2· --check --diffpreview, no writes 3· mergeapproved → main 4· one ansible-playbook run-i inventory.ini (paloalto + fortigate) Palo Alto group · panosaddress_object → security_rule COMMIT = the deliberate gatecandidate stays safe until you say go FortiGate group · fortiosfirewall_address → firewall_policy REST write = live immediatelyno commit; idempotent re-runs Same intent in group_vars → two vendor translations → reviewed, previewed, reversible review gatepipeline pathcommit gatekey insightlive/enforcing
Read left to right: a branch + PR (reviewed) → --check preview → merge → one run that drives panos (commit gate) and fortios (live REST) from the SAME inventory. Red = the gates that stop a bad change.

Why bother with all this rigour? Because the perimeter is a live target. In 2024, PAN-OS CVE-2024-3400 (GlobalProtect command injection, CVSS 10.0) was actively exploited in the wild, and CVE-2024-0012 let attackers bypass the management web interface auth. When the next advisory drops, the team that can review, dry-run and roll out a hardening rule across every firewall from code in minutes — with an audit trail — is in a very different place from the team clicking 40 GUIs by hand at 2 a.m. Rules-as-code isn't just tidiness; it's how you respond fast and prove what you did.

Figure 5 — Ansible firewall automation — the cheat-sheet
Ansible firewall automation on one card: the panos and fortios module map, connection model, commit and gate flow, gotchas and rollout commands A nine-tile cheat sheet. Tiles cover the Palo Alto module set, the Fortinet module set, the connection model for each, the candidate-commit model on PAN-OS, the no-commit live model on FortiGate, the two ordering and commit gotchas, rule shadowing and position, the safe rollout commands, and the one-inventory both-vendors idea. Ansible firewall automation — your one-glance card Palo Alto — panospanos_address_objectpanos_security_rulepanos_commit_firewallPanorama: + commit_panorama/push Fortinet — fortiosfortios_firewall_addressfortios_firewall_policyvdom (root), state: presentlegacy fortiosapi = deprecated Connection modelpanos: provider / httpapi (XML API)fortios: httpapi + access_token (REST)NEVER network_cli for thesetokens/keys → Ansible Vault PAN-OS commit modeledits → candidate configcommit → running (now live)inline commit: true = deprecatedno commit = rule not enforcing FortiGate modeleach REST write = live nowno candidate / no commit stepre-run = ok (idempotent)verify: show firewall policy N Top 3 gotchas1· PAN: forgot the commit2· Forti: object before policy3· rule shadowing (position)entry not found in datasource Safe rollout flowgit branch → PR reviewansible-playbook --check --diffmerge → run → gated commitgit revert = clean rollback One inventory, bothgroups: paloalto / fortigateintent once in group_varseach play → its vendor modules1 truth → 2 translations Cert angleRHCE EX294: playbooks, roles,collections, VaultPCNSE/NSE: automate the boxwhy CVE-2024-3400 → automate fast
Your one-card map: panos vs fortios module names, connection model, the commit/gate flow, the two big gotchas, and the safe-rollout commands. Keep it open in your first week and before any PCNSE/NSE or RHCE interview.
Daily-life analogy — the dabbawala two-line delivery

Your single change ('allow the partner host to the payment app') is one tiffin order. The group_vars is the address written once on the lid. The Mumbai dabbawala system reads that one address and routes it through whichever line is right — the panos line commits at the destination gate (the PAN-OS commit), the fortios line drops it straight in (live REST). One order, written once, delivered correctly to two different buildings by two different routes. That's one inventory driving both vendors.

Prove you own firewall-as-code

Cold, in 30 seconds: name the panos modules (panos_address_object → panos_security_rule → panos_commit_firewall) and the fortios modules (fortios_firewall_address → fortios_firewall_policy); say the connection model for each (panos = provider/httpapi XML API; fortios = httpapi REST + access_token; never network_cli); state the two big gotchas (PAN forgot-commit; Forti object-before-policy); and describe the safe rollout (git PR → --check → gated commit). If you can do that without notes, you're ready for the next lesson and for the automation questions in PCNSE/NSE and RHCE.

Next: Ansible Vault — encrypt those tokens & passwordsRelated: Ansible Playbooks for Cisco IOS
Quick check · Q4 of 10

An interviewer asks Sneha: "Give me the single most important reason to put firewall changes behind a git PR + --check + a gated commit, instead of pushing playbooks straight from your laptop." Best answer?

Correct: b. The whole value of rules-as-code is governance: a reviewed PR catches mistakes, --check previews impact, git gives an audit trail and git revert rollback, and the gated commit makes the live moment deliberate. It's not about speed, you still need objects, and Palo Alto still requires its commit step regardless of rollout flow.

🤖 Ask the AI Tutor

Tap any question — instant, scoped to this lesson. No login, no waiting.

Pre-curated from Ansible docs + community Q&A, scoped to this lesson. For a live prod issue, paste your export into chat.techclick.in.

📝 Wrap-up assessment — six more

You've answered 4 inline. Six left. 70% (7 of 10) marks the lesson complete on your profile. Tap Submit all answers at the end.

Q5 · Remember

Which Ansible collection and connection model does PAN-OS use, and what activates a rule it created?

Correct: a. PAN-OS is driven by paloaltonetworks.panos over its XML API (via a provider dict or httpapi), and edits sit in candidate config until a commit makes them live. fortinet.fortios is the FortiGate collection; cisco.ios/network_cli is for IOS switches; PAN-OS firewall automation does not use network_cli and does not need a reboot.
Q6 · Apply

Priya at Infosys must allow a partner host 203.0.113.40 to reach a payment app on a Palo Alto firewall. She writes a panos_address_object task then a panos_security_rule task and runs the play. The play succeeds but users still can't reach the app. What did she most likely forget?

Correct: a. Her tasks wrote to the candidate config; without a panos_commit_firewall (or commit + push on Panorama) the rule never reaches the running config, so it isn't enforcing yet. PAN-OS doesn't need a reboot, panos doesn't use network_cli, and panos_address_object is exactly how you create objects — she did create it.
Q7 · Apply

On a FortiGate (vdom 'root'), Rahul at TCS wants a policy that permits source object 'partner-host' to destination 'pay-app'. The policy task fails with 'entry not found in datasource'. What is the fix?

Correct: b. A FortiGate policy references address objects by name, so those fortios_firewall_address objects must exist first — ordering them above the policy task fixes the 'entry not found in datasource' error. FortiGate config has no separate commit step, fortios uses httpapi not network_cli, and 'global' is not where these firewall objects live.
Q8 · Analyze

A team's playbook adds an 'allow any-any for testing' rule at the TOP of the Palo Alto rulebase, above the existing tight rules. The play commits cleanly and traffic flows, but a later audit flags a security hole. What happened, in rulebase terms?

Correct: c. Firewalls evaluate top-down and stop at the first match, so a broad allow placed at the top (via location: 'top') shadows every stricter rule beneath it — a classic automation mistake when you don't control rule position. The commit clearly worked (traffic flowed), FortiOS isn't involved on a Palo Alto box, and idempotency doesn't delete unrelated rules.
Q9 · Analyze

Karthik runs the firewall playbook with ansible-playbook site.yml --check and it reports two tasks as 'changed'. He runs the real play, then runs --check again and now everything is 'ok'. What does this prove?

Correct: d. Idempotency means the playbook describes a desired state: the first real run created the rules, so a second --check finds reality already matches and reports 'ok' (no changes). Check mode never writes config (that's the point of --check), a steady 'ok' is healthy not broken, and the vdom didn't change.
Q10 · Evaluate

Two rollout designs for firewall-as-code. (A) Engineers push playbooks straight to the firewalls from their laptops, committing immediately, and keep a copy of the YAML in a shared folder. (B) Playbooks live in git; every change is a pull request reviewed by a second engineer, run with --check in CI, and the commit/push is a separate gated job after approval. Which is stronger and why?

Correct: a. B delivers exactly what manual GUI clicks lacked: a reviewed change (PR), a preview of impact (--check), a full history to audit and revert (git), and a deliberate moment to commit rather than an accidental one. A's laptop-push with an immediate commit and a shared folder reproduces the drift and 'who changed rule 47?' problem you were trying to kill; immediate commit removes the safety the candidate model gives you.
Lesson complete — saved to your profile.
Almost! You need 70% (7 of 10) — re-read the path that tripped you up and tap "Try again".

🧠 In your own words

Type one line: In one line, why is a Palo Alto rule created by Ansible not actually enforcing traffic until a separate step runs? Then compare to the expert version.

Expert version: Because PAN-OS edits land in the candidate configuration; they only start enforcing once a commit (e.g. panos_commit_firewall) copies candidate to the running config — so a panos playbook is incomplete without that commit task.

🗣 Teach a friend

Best way to lock it in — explain it in one line to a teammate. Tap to generate a paste-ready summary.

📖 Glossary

Rules as code
Defining firewall objects and policies in version-controlled files (playbooks) instead of clicking a GUI, so changes are reviewed, audited and repeatable.
paloaltonetworks.panos
The official Ansible collection for PAN-OS firewalls and Panorama; drives the device over its XML API, not the CLI.
fortinet.fortios
The official Ansible collection for FortiGate/FortiOS; drives the device over its REST API using the httpapi connection.
Candidate vs running config
On PAN-OS, candidate holds your edits; running is what actually enforces traffic. A commit copies candidate to running.
panos_commit_firewall
The panos module that commits a firewall's candidate config to running, making earlier rule/object tasks take effect.
provider (panos)
A dict of PAN-OS connection details (ip_address, username, password or api_key) passed to panos tasks; the modern alternative is an httpapi connection.
httpapi connection
An Ansible connection plugin that talks to a device's HTTP API. Both panos (optionally) and fortios use it instead of network_cli/SSH.
access_token (fortios)
A REST API token generated on FortiGate (System → Administrators → REST API Admin) that authenticates fortios tasks; safer than a reused admin password.
VDOM
Virtual Domain — a logical firewall instance inside one FortiGate. fortios tasks target a vdom (default 'root') so objects land in the right partition.
device_group (Panorama)
On Panorama, the container that holds shared objects and rules pushed to a set of managed firewalls; panos tasks set device_group to target it.
Object-before-policy
The ordering rule: an address/service object must exist before a policy can reference it by name, or the policy task errors out.
--check mode
Ansible dry-run: reports which tasks would change without writing anything, so you preview a firewall change before it is real.
Idempotency
Running the same playbook twice leaves the firewall in the same state — the second run reports 'ok', not 'changed', because the rule already matches.

📚 Sources

  1. paloaltonetworks.panos.panos_security_rule module — parameters (provider, device_group, rule_name, source_zone/destination_zone, source_ip/destination_ip, application, service, action, tag_name, location) and the deprecated inline 'commit'. paloaltonetworks.github.io/pan-os-ansible/modules/panos_security_rule_module.html
  2. paloaltonetworks.panos.panos_commit_firewall / panos_commit_panorama / panos_commit_push — committing candidate to running; Panorama commit then push to managed firewalls. paloaltonetworks.github.io/pan-os-ansible/modules/panos_commit_firewall_module.html
  3. fortinet.fortios.fortios_firewall_policy and fortios_firewall_address modules — vdom, access_token, srcintf/dstintf/srcaddr/dstaddr/service/action/schedule, httpapi connection; legacy fortiosapi deprecated in favour of httpapi. docs.ansible.com/.../fortinet/fortios/fortios_firewall_policy_module.html
  4. PaloAltoNetworks/pan-os-ansible GitHub issue #569 — real gotcha: after upgrading Panorama to 11.x, panos_commit_panorama reports success but the config stays on candidate and isn't pushed (was fine on 10.2.8). github.com/PaloAltoNetworks/pan-os-ansible/issues/569
  5. OneUpTime engineering blog — 'How to Use Ansible to Manage FortiGate Firewalls' (Feb 2026): generating a REST API Admin token under System → Administrators, ansible_httpapi setup, address-then-policy ordering. oneuptime.com/blog/post/2026-02-21-how-to-use-ansible-to-manage-fortigate-firewalls/view
  6. PAN-OS CVE-2024-3400 (GlobalProtect command injection, CVSS 10.0, actively exploited) and CVE-2024-0012 (mgmt-web auth bypass) — why fast, repeatable, auditable firewall change/patching via code matters. security.paloaltonetworks.com/CVE-2024-3400 · cisa.gov alert 2024/04/12
  7. Red Hat RHCE EX294 objectives (manage Ansible content, write playbooks with tasks/variables, use roles and collections, encrypt with Ansible Vault) + Palo Alto PCNSE / Fortinet NSE automation angle. redhat.com/en/services/training/ex294-red-hat-certified-engineer-rhce-exam-red-hat-enterprise-linux-8-exam

What's next?

You just put firewall passwords and API tokens into variables — now stop them sitting in plaintext. Next we encrypt them with Ansible Vault so your rules-as-code repo is safe to share.