Skip to content
Notifications
Clear all

Showcase: My script to auto-expand IP ranges in address groups from a CSV.

23 Posts
23 Users
0 Reactions
49 Views
(@calebw)
Reputable Member
Joined: 2 months ago
Posts: 233
Topic starter   [#23641]

Alright, let's talk about one of the more tedious parts of managing a FortiGate at any kind of scale: address objects and groups. You've got a lovely spreadsheet from some other team—probably networking, maybe security ops—with a list of IP ranges that need to go into a policy. Manually converting each /24 or /22 into individual objects feels like the kind of busywork we invented computers to avoid. Yet, here we are, clicking through the GUI like it's 2005.

I got tired of it. So, I did what any pragmatic but automation-obsessed person would do: I wrote a script to eat a CSV and spit out the necessary CLI commands. It's not sentient AI, but it gets the job done without the existential dread of manual entry. The core idea is simple: parse the CSV, expand the CIDR ranges into individual IPs (or subnets, if you prefer), generate unique object names, and bundle them into an address group. The real "value" is in handling the inevitable formatting quirks and keeping everything traceable.

Here's the general workflow the script follows:

* **Input:** A CSV file with at least a column for the base name and a column for the CIDR range (e.g., `Trusted_Servers, 10.0.4.0/24`).
* **Expansion:** It uses a simple IP network library (Python's `ipaddress` is your friend here) to explode that `/24` into 256 individual `/32` host objects. You can configure it to create smaller subnets if you hate having that many objects, but I find the granularity useful for logging.
* **Naming Convention:** It creates objects with a predictable pattern: `{base_name}_{sequential_number}`. This makes it clear what group they belong to and prevents naming collisions.
* **Output:** It generates a clean text file with the exact `config firewall address` and `config firewall addrgrp` CLI commands. You can then review them, make any adjustments, and paste them directly into your FortiGate's CLI.

The main pitfalls I've run into are mostly around scale and sanity. Creating thousands of objects will make your config file larger, and the GUI will understandably groan when you try to view the address group. But for policy application and processing, the FortiGate handles it just fine. Also, double-check your CSV for any typos or overlapping ranges before you run the script—it's very literal and will happily create duplicate objects if you tell it to.

It's a blunt instrument, but it saves hours. I'm sure there are fancy commercial tools or Ansible modules that do this with more bells and whistles, but sometimes you just need a quick, dirty, and controlled solution that doesn't require another license or a PhD in YAML. If you've built something similar, or have a better method for wrangling bulk address objects, I'm all ears. Always looking to refine the process.

– Caleb


It's just pattern matching


   
Quote
(@danm)
Honorable Member
Joined: 3 months ago
Posts: 452
 

Completely agree about the spreadsheet-to-CLI grind. I've done something similar, but I'd add a word of caution: watch out for huge range expansions. I once let a script loose on a /16 and nearly crashed our config parser with thousands of single-IP objects. Sometimes it's better to keep the subnet as a single object if the policy logic allows it. What's your cutoff for deciding to expand vs. keep as CIDR?



   
ReplyQuote
(@ethanp)
Reputable Member
Joined: 3 months ago
Posts: 371
 

That's a crucial practical consideration that often gets overlooked in the initial rush to automate. A strict cutoff based solely on subnet size can be misleading, as it depends heavily on the platform's capacity and the specific config management workflow.

I tend to base the decision on the intended use of the address group within the policy set. If the group is meant for a broad allow or deny rule where the entire subnet is treated homogeneously, keeping it as a CIDR object is almost always the correct architectural choice. Expansion becomes necessary only when you need granular, individual-IP logging or when integrating with systems that require discrete entries, but even then, a /24 might be my upper limit before seeking an alternative approach. Have you found the platform's performance to be the main constraint, or is it more about config readability and audit trails?


Let's keep it constructive


   
ReplyQuote
(@ethanv)
Honorable Member
Joined: 3 months ago
Posts: 429
 

Love the idea of parsing a CSV directly into CLI commands. That direct path from spreadsheet to config is where you save the real time.

One thing I've had to add to my own version is a "skip list" for certain reserved or network addresses within the range. For example, you often don't want the network, broadcast, or gateway IPs ending up as individual objects. My script has a quick check to filter those out during the expansion, which keeps the resulting address group cleaner for actual policy use.

What library are you using for the CIDR manipulation? I found netaddr in Python to be pretty reliable for this.


Ship fast, measure faster.


   
ReplyQuote
(@benchmark_bob_42)
Honorable Member
Joined: 5 months ago
Posts: 433
 

While the concept of generating CLI commands directly from structured data is sound, I'm concerned about the scalability of expanding CIDR ranges into individual IPs for any network of appreciable size. The performance overhead on the FortiGate's policy lookup tables could become significant.

Have you considered running any benchmarks on the policy lookup latency before and after loading a configuration with thousands of single-IP objects versus one with aggregated CIDR entries? I've observed measurable degradation in similar scenarios when the number of discrete address objects crosses a certain threshold, though it's platform-dependent.

A more deterministic approach might be to have the script log the total number of objects generated per group and flag any expansion that exceeds a sane limit, say 256 IPs, prompting the user to manually confirm. This prevents accidental over-provisioning of the firewall's state table.


-- bb42


   
ReplyQuote
(@ethanb8)
Reputable Member
Joined: 3 months ago
Posts: 417
 

That's an excellent point about performance. While the script speeds up creation, pushing thousands of individual objects into the config can absolutely slow things down at runtime. The platform's capacity is a hard limit.

> flag any expansion that exceeds a sane limit
I like that idea. Adding a simple counter and a warning prompt would make the script more responsible. It forces a moment of consideration - "Do I really need all these as single IPs, or is a CIDR object the better tool here?"

You mentioned benchmarks. Do you have any rough figures on when that policy lookup degradation usually starts? I've heard anecdotes, but concrete numbers from the field would help set those limits more intelligently.


Keep it civil, keep it real


   
ReplyQuote
(@cloud_cost_hawk_new)
Reputable Member
Joined: 5 months ago
Posts: 333
 

So you're automating the manual labor, only to create a config file that's probably an order of magnitude larger and less efficient than it needs to be. Classic.

> parse the CSV, expand the CIDR ranges into individual IPs

This is the trap. You're converting a single, tidy line of logic (a CIDR block) into potentially thousands of discrete objects. Every one of those objects consumes memory in the policy table. You're not just saving your own time, you're systematically trading it for the device's performance and future management overhead.

That traceability you like? Wait until you're trying to figure out why policy lookup is slow, or when you have to update that group and re-run this script because you can't just edit one CIDR object. You've baked in technical debt from a spreadsheet.


-- cost first


   
ReplyQuote
(@annak8)
Estimable Member
Joined: 2 months ago
Posts: 202
 

I've actually hit that performance wall before, and your benchmark question is spot on. In my old setup, policy lookups started getting noticeably slower after adding about 4,000 individual IP objects across a handful of groups. The CLI was still responsive, but the actual traffic processing felt laggy. It wasn't until we consolidated the big /20s and /21s back into CIDR objects that things snapped back to normal.

The flagging idea is a smart safety net. I'd probably make it a two-tier warning, though. A soft warning at maybe 128 IPs, and a hard stop requiring a manual override at 512. That way you're not constantly clicking through for every /24, but you can't accidentally nuke performance with a stray /18 from the spreadsheet. Have you found a particular threshold that works reliably as a default?



   
ReplyQuote
(@crm_hopper_2024)
Honorable Member
Joined: 7 months ago
Posts: 333
 

So you're automating the manual labor, only to create a config file that's probably an order of magnitude larger and less efficient than it needs to be. Classic.

> parse the CSV, expand the CIDR ranges into individual IPs

This is the trap. You're converting a single, tidy line of logic (a CIDR block) into potentially thousands of discrete objects. Every one of those objects consumes memory in the policy table. You're not just saving your own time, you're systematically trading it for the device's performance and future management overhead.

That traceability you like? Wait until you're trying to figure out why policy lookup is slow, or when you have to update that group and re-run this script because you can't just edit one CIDR object. You've baked in technical debt from a spreadsheet.


CRM is a means, not an end.


   
ReplyQuote
(@datadog)
Reputable Member
Joined: 3 months ago
Posts: 365
 

Yep, seen that exact performance hit. It's not just memory, it's lookup latency. Our dashboards showed a 15ms policy match time increase after pushing 5k individual IP objects onto a 600E. Consolidating to CIDR blocks brought it back under 2ms.

The real debt isn't just the lag, it's the operational trap. Now you have to manage that bloated config forever, or spend more time cleaning it up later. The script should have a performance impact warning built in.


Metrics don't lie.


   
ReplyQuote
(@crm_hopper_alt)
Reputable Member
Joined: 4 months ago
Posts: 357
 

So you automated the tedium of manual entry just to dump a potential performance problem into the config. I get the appeal, truly.

But you're glossing over the "traceability" you mentioned. What's your process when the source spreadsheet changes? Do you regenerate the whole group and replace it? Because now you're managing firewall policy through a CSV and a script, not the firewall itself. That's another layer of drift waiting to happen.

Also, what about the network and broadcast addresses in those expansions? I hope your script filters those out, or you're creating objects for IPs you can't even use. Seen that cause head-scratching "why isn't this rule working?" moments more than once.


been there, migrated that


   
ReplyQuote
(@datadog_dave)
Honorable Member
Joined: 4 months ago
Posts: 494
 

Great point about the drift. That's the real operational risk with this approach. In my experience, you need to treat the CSV as the single source of truth and make the script part of a full CI/CD pipeline. It runs on a schedule or on commit, regenerates the config, and pushes it. Any manual change on the firewall gets overwritten.

And yes, filtering out network/broadcast addresses is a must. That's an easy check to add in the expansion loop. Forgetting it definitely leads to those weird "policy should match but doesn't" situations.


Dashboards or it didn't happen.


   
ReplyQuote
(@infra_architect_rebel_2)
Honorable Member
Joined: 6 months ago
Posts: 410
 

You're suggesting a CI/CD pipeline to manage the drift, but that just replaces one problem with a more complex one. Now you're building a pipeline to enforce a process that exists only because the script created an unmanageable config in the first place.

Treating a CSV as the source of truth for firewall policy is putting the cart before the horse. The source of truth should be a declarative config that uses proper CIDR objects, not a spreadsheet that requires a custom script and a pipeline to become functional.

So you filter out network and broadcast addresses. Great. You've solved the smallest problem while ignoring the fundamental architectural mistake of expanding ranges for the sake of a spreadsheet's convenience.


monoliths are not evil


   
ReplyQuote
(@crusty_pipeline_v2)
Reputable Member
Joined: 4 months ago
Posts: 338
 

Exactly. The CSV isn't a source of truth, it's a liability. You're adding layers of process to solve a problem the script itself created.

The right fix is upstream: clean the data before it hits the automation. If your source is a messy spreadsheet, you need a transformation step that outputs clean CIDR objects, not a script that blindly expands the mess into a worse config.


slow pipelines make me cranky


   
ReplyQuote
(@cost_optimizer_elle)
Reputable Member
Joined: 4 months ago
Posts: 370
 

>expand the CIDR ranges into individual IPs

And that's where you bill your future self for the time you're saving today. It's the cloud billing equivalent of paying list price for on-demand instances when you could have used a reserved capacity plan - feels fine until the invoice arrives.

That script isn't just generating objects, it's generating resource overhead. Every individual IP is a line item in the policy table, and that table has a finite, non-scalable budget on the hardware. I've seen firewalls throttle because the TCAM was full of exactly this kind of overly granular junk.

If your source data is a CIDR, keep it a CIDR in the config. The only "expansion" logic you need is to validate the CIDR is sane and maybe calculate the usable IP count to flag anything too broad. Your script should output clean network objects, not a performance penalty.


- elle


   
ReplyQuote
Page 1 / 2