Parse FortiGate and FortiOS full-configuration or backup exports into the shared firewall schema. Use when input contains config/edit/set/next/end blocks, VDOM, firewall policy or address, srcintf, dstintf, UTM profiles, or VIPs, including audit, conversion, diff, summary, and explanation tasks.
설치
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
Parse FortiGate and FortiOS full-configuration or backup exports into the shared firewall schema. Use when input contains config/edit/set/next/end blocks, VDOM, firewall policy or address, srcintf, dstintf, UTM profiles, or VIPs, including audit, conversion, diff, summary, and explanation tasks.
Use this skill to parse Fortinet FortiGate / FortiOS backup or show full-configuration output into the shared vendor-neutral firewall intermediate schema. It focuses on nested config / edit / set / next / end blocks, including VDOMs, interfaces, zones, firewall addresses and services, policies, central SNAT, VIPs, routes, VPN, HA, profiles, and system settings.
FortiOS behavior is version- and feature-dependent. Preserve unknown blocks in residual_raw, capture VDOM context, and flag policy/NAT/profile constructs that cannot be mapped cleanly to the intermediate schema.
Scope and routing
Use only for FortiGate or FortiOS block syntax. Hand off ASA/FTD access-list, nameif, or object network input to parsing-cisco-configs, PAN-OS XML or set deviceconfig to parsing-palo-configs, and Junos hierarchy or set security to parsing-srx-configs. Verify production-bound results against current device documentation and output. Downstream consumers are the audit, conversion, and diff skills.
Input Format
FortiOS configs use a hierarchical block format:
config <section>
edit <name-or-id>
set <key> <value>
set <key> <value>
config <sub-section>
edit <sub-name>
set <key> <value>
next
end
next
edit <name-or-id>
...
next
end
Key syntax rules:
Values with spaces are quoted: set comment "Allow web traffic"
Multi-value fields use space-separated values: set srcaddr "addr1" "addr2"
Full block-syntax rules: references/config-format.md
Building the Config Tree
Parse the block format into a nested object tree:
Track a stack of current context (section path + edit name)
config <X> → push section name
edit <N> → push entry name (strip quotes)
set <key> <value> → store key-value at current depth
next → pop entry
end → pop section
Extraction Pipeline
1. Zones and Interfaces
Zones:config system zone → edit <name> with set interface <list>Interfaces:config system interface → edit <name> with set vdom, set ip, set type, set vlanid
Additional interface fields to extract:
After all interfaces parsed, back-populate lag_members on aggregate interfaces
Critical: Interface-as-Zone Merging
FortiGate can use interface names directly as zones in policies (via srcintf/dstintf).
If a policy references an interface name not in any zone, treat that interface as its own zone.
Merge zones and interfaces: create a zone entry for each interface used as a zone.
Zone Building Priority 3: Unzoned Interfaces
After explicit zones and policy-referenced interfaces, create auto-zones for any interface that has an IP address (or is a DHCP client) but was not yet assigned to a zone.
Allowaccess Classification:
Classify allowaccess values into management services (ssh, https, http, telnet, ping, snmp, netconf, fgfm, fmg-access, ftm, radius-acct, security-fabric, fabric, capwap, speed-test) and routing protocols (ospf, bgp, rip, isis, bfd). Attach to zones as host_inbound data.
2. Address Objects
Path: config firewall address → edit <name>
Types — detect from set type or infer from fields:
set type ipmask + set subnet <ip> <mask> → type: "subnet" (convert mask to CIDR); promote a /32 (or IPv6 /128) result to type: "host"
set type iprange + set start-ip / set end-ip → type: "range", value: "start-end"
set type fqdn + set fqdn <domain> → type: "fqdn"
set type geography + set country <code> → type: "geo" (warn: limited cross-platform support)
set type wildcard + set wildcard <ip> <mask> → type: "wildcard" (preserve the <ip> <mask> wildcard value; "network" is NOT a valid schema type) with info warning
set type wildcard-fqdn + set wildcard-fqdn <pattern> → type: "fqdn" (convert from wildcard-fqdn with info warning)
Also extract: set comment, set associated-interface.
Convert subnet mask notation (255.255.255.0) to CIDR (/24).
Auto-detect IP version.
set ip6 <ipv6prefix/len> → type: "subnet" (or "host" if /128)
IPv6 Address Groups:config firewall addrgrp6 → edit <name>
set member <list>
3. Address Groups
Path: config firewall addrgrp → edit <name>
Extract: set member <list> (space-separated quoted names)
4. Service Objects
Path: config firewall service custom → edit <name>
Extract from:
set protocol TCP/UDP/SCTP + set tcp-portrange <range> / set udp-portrange <range> / set sctp-portrange <range>
set protocol ICMP + set icmptype / set icmpcode
set protocol IP + set protocol-number <n> → preserve the IP protocol number <n> (e.g. GRE=47, ESP=50) as the service object's protocol (the numeric value, or via a warning). Only emit protocol: "any" when set protocol IP genuinely means all IP protocols (no protocol-number, or protocol-number 0).
set protocol ICMP6 → protocol: "icmpv6"
Port range format: 80 or 80-443 or 80:1024-65535 (dst:src)
Note: A single custom service can set ANY combination of tcp-portrange, udp-portrange, and sctp-portrange simultaneously. Split into separate service objects — one TCP, one UDP, and/or one SCTP — according to which ranges are present.
5. Service Groups
Path: config firewall service group → edit <name>
Extract: set member <list>
6. Security Policies
Path: config firewall policy → edit <id>
For each policy extract:
name — set name <value> (FortiGate uses numeric IDs as primary key, name is optional)
src_zones — set srcintf <list> (interface or zone names)
dst_zones — set dstintf <list>
src_addresses — set srcaddr <list>
dst_addresses — set dstaddr <list>
services — set service <list>
applications — set application <list> (application control IDs/names)
action — set action accept → "allow", set action deny → "deny"
log_start — set logtraffic-start enable (start logging is controlled separately from logtraffic)
log_end — set logtraffic all. set logtraffic utm logs UTM/security events only, NOT end-of-session traffic — map to log_end: false and note UTM-only logging in metadata.warnings
disabled — set status disable
description — set comments <value>
schedule — set schedule <value>
source_users — set groups <list> (FSSO groups)
Policy NAT (do NOT emit a policy nat field — the policy schema has no nat field):
FortiGate per-policy source NAT (set nat enable, optionally with set ippool enable + set poolname <pool>) must be translated into a nat_rules[] source-NAT entry, NOT a flag on the policy:
set nat enable alone → source-NAT (interface/egress overload) on the policy's dstintf, scoped to the policy's srcaddr/dstaddr.
set nat enable + set ippool enable + set poolname <pool> → source-NAT using the named IP pool <pool> (cross-reference config firewall ippool).
If the source-NAT intent cannot be resolved to a concrete nat_rules[] entry, preserve it as a metadata.warnings / residual_raw note rather than inventing a policy field.
Default values when fields are omitted from config:
action defaults to accept (→ "allow")
logtraffic defaults to utm on accept policies (→ log_end: false; UTM-event logging only)
status defaults to enable (→ disabled: false)
UTM / Security Profiles — when set utm-status enable:
set av-profile <name> → antivirus
set webfilter-profile <name> → URL filtering
set ips-sensor <name> → IPS/IDP
set application-list <name> → application control (do NOT emit an application-list security-profile key — it is not a schema-supported profile key; signal app-control presence via security_services.app_id at device level and/or store the profile reference in security_profile_objects / metadata.warnings)
set ssl-ssh-profile <name> → SSL inspection
set dnsfilter-profile <name> → DNS filtering
set emailfilter-profile <name> → email filtering
set dlp-profile <name> → DLP
set profile-group <name> → profile group (overrides individual profiles)
7. NAT Rules
Source NAT (IP Pools):config firewall ippool → edit <name>
Extract: set startip, set endip, set type (overload, one-to-one, fixed-port-range), set associated-interface <name> — binds pool to specific egress interface
Central SNAT:config firewall central-snat-map → edit <id>
Extract the full FortiOS central-SNAT field set so real rules are not missed:
set srcintf <list> — source interface(s)
set dstintf <list> — destination/egress interface(s)
set orig-addr <list> — original (pre-NAT) source addresses
set dst-addr <list> — destination addresses the rule matches
set nat enable|disable — whether this entry performs NAT (a disabled entry = no-NAT exemption; preserve it)
set nat-ippool <list> (also set natippool variant) — translated source pool; absent → egress-interface overload
set protocol <n> and set orig-port / set nat-port where present — protocol/port scoping
Map to a nat_rules[] source-NAT entry (or a no-NAT exemption when nat disable).
Destination NAT (VIPs):config firewall vip → edit <name>
Extract: set extip (original dest), set mappedip (translated dest),
set extintf, set portforward enable + set extport / set mappedport
Note: VIPs are referenced in policies via set dstaddr <vip-name>
8. Schedules
Recurring:config firewall schedule recurring → edit <name>
Extract: set day, set start, set endOne-time:config firewall schedule onetime → edit <name>
Extract: set start, set endGroup:config firewall schedule group → edit <name>
Extract: set member
9. Application Mapping (L7 → Canonical)
FortiGate supports L7 application control via set application <list> on policies. These reference
FortiOS application IDs or names from the application control database.
Extracting application references from policies:
set application <list> — space-separated application IDs or names
set application-list <name> — references an application control list profile (separate from direct app match)
Resolving FortiOS application names to canonical:
FortiOS Name
Canonical App
Category
HTTPS
https
web
HTTP
http
web
SSH
ssh
remote-access
RDP
rdp
remote-access
DNS
dns
network-mgmt
SMTP
smtp
email
NTP
ntp
network-mgmt
SNMP
snmp
network-mgmt
FTP
ftp
file-transfer
TFTP
tftp
file-transfer
SIP
sip
voip
LDAP
ldap
auth
Kerberos
kerberos
auth
SMB
smb
file-transfer
MySQL
mysql
database
MSSQL
mssql
database
PostgreSQL
postgresql
database
MongoDB
mongodb
database
Zoom
zoom
collaboration
Microsoft.Teams
ms-teams
collaboration
Slack
slack
collaboration
YouTube
youtube
streaming
Netflix
netflix
streaming
On policy output: When set application values are resolved, populate the policy's apps array
with { vendor_name: "HTTPS", canonical: "https", confidence: 1.0, category: "web" }.
The services array keeps any set service matches separately.
Application control list profiles (config application list) define grouped app-control
policies. These do not map 1:1 to application groups — they are UTM profiles that filter
applications by category, risk, or specific app ID. The schema has no application-list
profile key — represent app-control presence via security_services.app_id (device-level)
and store the profile reference in security_profile_objects and/or metadata.warnings.
Do not try to decompose the list into individual apps.
Unresolvable apps: FortiOS numeric app IDs without a known name mapping → set confidence: 0.0,
preserve the ID as vendor_name, and warn.
9b. Application Groups
FortiOS does not have explicit application groups in the same way PAN-OS does. The closest
equivalent is config application group which groups application control signatures.
Path: config application group → edit <name>
Extract: set application <list> — member application IDs/names.
Resolve each member to canonical. Store in application_groups array.
If a group contains a mix of L7 apps and port-based services, split them appropriately.
10. Security Profile Definitions
Parse full profile objects for reference:
config antivirus profile
config webfilter profile
config ips sensor
config application list
config firewall ssl-ssh-profile
11. Routing
Static routes (IPv4):config router static → edit <id> with set dst, set gateway, set device, set distance
Static routes (IPv6):config router static6 → edit <id> with same fields using IPv6 prefixes
BGP:config router bgp — extract:
set as, set router-id, global set keepalive-timer/set holdtime-timer
Per-neighbor (in config neighbor): remote-as, description, update-source, password (record presence only — redact the value, never emit it), per-neighbor timers (override global), next-hop-self, soft-reconfiguration, route-reflector-client, status enable|disable
config network entries (prefix advertisements)
config redistribute with set status enable|disable
Warn: route-map/prefix-list references are not converted
OSPF:config router ospf — extract:
set router-id, set auto-cost-reference-bandwidth
config area: area ID, type (stub/nssa with no-summary), default-cost, authentication
config ospf-interface: area assignment, passive flag, cost, priority, hello/dead intervals, network-type (point-to-point/broadcast), MD5 authentication with key ID (record key presence only — redact the key value)
DHCP Server:config system dhcp server — extract top-level fields (set default-gateway, set netmask, set interface, set domain, set lease-time, set dns-server1/dns-server2) plus nested config ip-range (start-ip/end-ip) and config reserved-address (mac, ip, description). Derive network CIDR from gateway + netmask.
FortiOS compound proposal parsing for VPN:
FortiOS encodes IKE/IPsec proposals as compound strings. Do NOT assume every proposal is a simple
encryption-integrity pair — parse encryption / integrity / prf separately:
AEAD GCM (no separate integrity):aes256gcm, aes128gcm, chacha20poly1305 carry their own
authentication — integrity is N/A. Phase2 GCM is the bare token (e.g. aes256gcm, no integrity suffix).
IKEv2 phase1 PRF suffix (phase1-only):aes256gcm-prfsha384 → encryption aes256gcm, prf sha384,
no separate integrity. The prf* suffix appears only on phase1 proposals, never phase2.
null:null encryption (no confidentiality) and null integrity also exist — flag as weak.
Multiple proposals are space-separated: aes256-sha256 aes128-sha1 aes256gcm. Tokenize each on -,
then classify each segment as encryption, integrity, or prf rather than assuming a fixed two-part split.
VPN IPsec:
config vpn ipsec phase1-interface: IKE version, remote-gw, proposal (compound strings — see "FortiOS compound proposal parsing" above), DH group, key lifetime, PSK presence (mask the value as "****" per the schema — never emit the raw key), certificate auth detection
Phase1 name auto-creates a tunnel interface of the same name
Resolve tunnel IP from matching interface, associate static routes through tunnel interfaces
Flag weak algorithms (DES/3DES, MD5, DH group ≤ 5)
Warn: certificate-based auth not fully converted
13. Tokenizer
Handle quoted multi-value lines correctly: set member "HOST_A" "HOST_B" → ['set', 'member', 'HOST_A', 'HOST_B']. Strip quotes during tokenization. Values containing spaces must be kept as single tokens when quoted.
14. Residual Config Capture
Capture unrecognized config sections verbatim with depth tracking. Categorize into: VPN/IPsec, Routing Protocols, AAA/Users, PKI/Certificates, Wireless, Switching, DNS, NTP, SNMP, Other. Store in residual_raw for manual review.
15. Multi-VDOM
Detect VDOM context: config vdom / edit <vdom-name>.
Each interface has set vdom <name>. Parse per-VDOM, tag items, merge.
16. Implicit Rules
After parsing all explicit policies, append:
Per-zone Intra-zone rules ONLY for explicit config system zone entries carrying
set intrazone deny|allow. Do NOT fabricate intra-zone rules for raw interface auto-zones
(interfaces never declared in config system zone).
Implicit: Default Deny — action: "deny", all any, _implicit: true
Implicit-rule name values (e.g. "default-deny", "Implicit: Default Deny") are free-form labels; consumers must match implicit rules on _implicit: true, never on the name.
Output Format
Present results in the intermediate schema format documented in references/intermediate-schema.md.
Note: schema sections not yet populated by this pipeline (e.g., security_profile_objects, routing_contexts) are emitted empty ([]/{}); any unhandled source constructs are captured in residual_raw rather than dropped.
Full intermediate-schema emission is optional for single live-device work. The complete JSON schema exists primarily for cross-vendor conversion and multi-config diffing. When interpreting or auditing a single live device pulled via SSH/API for an ops/audit task, it is fine to reason directly from the raw FortiOS config and skip full schema emission — extract the sections relevant to the question. Emit the full schema when the parse will feed firewall-config-conversion, firewall-config-diff, or another config for comparison.
Parser Quality Gates
Before returning a parse, run these common quality gates and include the results in the response:
Format and scope detection — report detected vendor, platform family, config format, version clues, virtual context names (VDOM/vsys/logical-system/routing-instance), and whether input appears complete or partial.
Schema conformance — emit the vendor-neutral JSON sections defined in references/intermediate-schema.md; use empty arrays/objects for absent sections rather than omitting expected top-level keys.
Object counts — summarize counts for zones, interfaces, address objects/groups, service/application objects/groups, policies, NAT rules, routes, VPNs, HA, admin users, and residual blocks.
Reference resolution — list unresolved object, service/application, zone/interface, profile, route, VPN, and NAT references with source rule/context where possible.
Ordering preservation — preserve security policy order, NAT order, route order when relevant, and inherited/pre/post/global ordering metadata with _rule_index or a vendor-specific context field.
State preservation — preserve disabled/inactive objects and rules, comments/descriptions, tags, schedules/time-ranges, negation flags, logging settings, and profile attachments.
Residual capture — put unsupported or ambiguous source lines/blocks into residual_raw with enough context for manual review. Do not silently drop unknown syntax.
Warnings and assumptions — populate metadata.warnings with parser limitations, partial-input assumptions, ambiguous conversions, and version-specific caveats.
Conversion readiness — if the user asks for migration/conversion, explicitly separate parsed facts from proposed target-platform design choices and call out non-isomorphic features.
A high-quality parse is not just valid JSON: it must make uncertainty visible. Prefer a complete parse with warnings and residuals over a clean-looking parse that hides unsupported constructs.
Analysis Checks
After extraction, run these checks and report findings:
Unused objects — addresses/services not referenced by any policy
Shadowed policies — rules fully covered by earlier rules
Overly permissive — rules with "all" in src+dst addresses and services
Missing logging — set logtraffic disable or set logtraffic utm on permit rules
Disabled policies — set status disable
Duplicate objects — same value, different names
Empty groups — groups with no members
VIP references — VIPs used as dst addresses (flag for NAT review)
Geography objects — limited cross-platform support
references/fixture-expected-output.json — Expected high-level intermediate-schema output for the minimal fixture
Secret Handling
Never emit secrets raw. IKE/VPN pre-shared keys, routing-protocol authentication keys (BGP/OSPF), and user password hashes must be masked as "****" (or reduced to a presence flag) with a metadata.warnings entry noting the redaction — matching the shared-schema convention ("psk": "****").
Common Pitfalls
Do not assume every policy interface is a zone; FortiGate policies can reference zones or raw interfaces.
VIP objects often imply destination NAT when referenced by policies; preserve both policy and NAT intent.
Central SNAT and per-policy NAT are different models; detect both and warn when both are present.
Profile groups and individual UTM profiles must be expanded while preserving unmapped FortiGate-specific profile fields.
Never emit raw secrets: mask VPN PSKs as "****" and represent BGP neighbor passwords and OSPF MD5 keys as presence flags plus a metadata.warnings note — shared secrets, passwords, and keys must never appear verbatim in parse output.
Verification Checklist
Input vendor/platform and config format were detected correctly
All major object counts are reported: zones, interfaces, addresses, services/applications, policies, NAT, routes, VPN, HA, and system settings
Output conforms to references/intermediate-schema.md
Disabled/inactive rules and objects are preserved with explicit state
Unresolved references, unsupported blocks, and parser assumptions are listed in metadata.warnings and/or residual_raw
Rule order and NAT order are preserved with _rule_index or equivalent ordering metadata
Cross-vendor conversion caveats are called out before suggesting target-platform config
No raw secrets in output — PSKs masked as "****", BGP/OSPF passwords and keys reduced to presence flags with warnings