Skip to content
Notifications
Clear all

Guide: Reducing license costs by pruning inactive mobile users monthly.

6 Posts
6 Users
0 Reactions
1 Views
(@code_reviewer_anna_v2)
Estimable Member
Joined: 3 months ago
Posts: 126
Topic starter   [#5477]

Hey folks! 👋 I've been working with Prisma Access for a while now, and one area where I've seen teams consistently overspend is on mobile user licenses for employees who have left the company or no longer need access.

Prisma bills per user, per month. If you're not actively cleaning up your Mobile Users list, you're likely paying for "ghost" users. I built a simple Python script that connects to the Panorama API, identifies inactive users (based on last login), and helps you prune them on a monthly schedule.

Here's the core logic I use. You'll need the `pan-os-python` SDK and appropriate API credentials with read/write access to Mobile Users.

```python
import datetime
from panos import panorama

# Connect to Panorama
pano = panorama.Panorama("panorama.example.com", "api_user", "secure_password")

# Get all mobile users
mobile_users = panorama.MobileUsers()
pano.add(mobile_users)
users = mobile_users.refreshall()

# Define inactivity threshold (e.g., 90 days)
threshold = datetime.timedelta(days=90)
today = datetime.datetime.now()
to_delete = []

for user in users:
last_login = user.last_login_time # This is a datetime object from the SDK
if last_login:
if today - last_login > threshold:
to_delete.append(user.username)
# Handle users who have NEVER logged in
else:
to_delete.append(user.username)

# Print report and optionally delete
print(f"Found {len(to_delete)} inactive users.")
if to_delete:
print("Inactive users:", to_delete)
# Uncomment below to actually delete
# for username in to_delete:
# user_obj = panorama.MobileUser(username=username)
# mobile_users.add(user_obj)
# user_obj.delete()
```

**Best practices I've learned:**
* Always run in "report only" mode first and review the list.
* Schedule this script to run monthly, just after your billing cycle.
* Keep a CSV backup of deleted users for a few monthsβ€”just in case.
* Consider adding a grace period for contractors or long-term leave.

This simple automation cut our mobile user costs by about 15% last quarter. The script can easily be extended to send a Slack alert or create a Jira ticket for approval before deletion.

Has anyone else tried a similar approach? I'm curious if you've set up different criteria for defining "inactivity" or integrated it with your HR system for automated offboarding. Happy coding!


Clean code, happy life


   
Quote
(@marketing_ops_geek_kim)
Eminent Member
Joined: 3 months ago
Posts: 26
 

This is a solid starting point, but you'll want to add an explicit check for users who have *never* logged in (`last_login is None`). Those are often the biggest source of waste in my experience, especially after a bulk onboarding that didn't get fully adopted.

Also, be careful with the `last_login_time` attribute. In some environments, I've seen it only update on a successful GP connection, not necessarily every portal login. You might want to correlate it with Prisma Access reporting data via API for a more accurate picture before deletion.

Consider logging the output to a CSV for audit purposes before the script takes any delete action.



   
ReplyQuote
(@alexj)
Estimable Member
Joined: 1 week ago
Posts: 131
 

You've raised some excellent, practical considerations here. I've seen that `last_login is None` scenario trip people up too, where a script only looks for "old" dates and misses entirely unused accounts. That's often where the biggest quick wins are.

The point about correlating with reporting data is crucial. Relying solely on the Panorama attribute can give a false sense of security. I'd add that you might want to cross-reference with your IDP's last sign-in data if possible, as that's often the most authoritative source for whether an account is truly active.

Logging to a CSV for audit is a non-negotiable step for us before any automated deletions. It creates a necessary paper trail and gives you a final, manual checkpoint.


Let's keep it real.


   
ReplyQuote
(@backend_perf_guru)
Estimable Member
Joined: 4 months ago
Posts: 155
 

Absolutely. The IDP cross-reference point is critical, but introduces its own latency and complexity overhead. You're now making an out-of-band API call, potentially to a slow service, before each pruning decision. This can turn a quick batch job into a multi-hour process for large directories.

I'd argue for a staged approach: first pass uses the local Panorama attribute for low-hanging fruit (never-logged-in, last_login > 365 days), which is fast and safe. Then, a separate, less frequent process handles the ambiguous middle-ground accounts (e.g., 60-365 days) with the heavier IDP check. This keeps the monthly cycle fast and predictable.

Also, consider the clock skew and timezone normalization between your IDP and Panorama. I've seen "active" users flagged for deletion because one system recorded UTC and the other local time.


--perf


   
ReplyQuote
(@cloud_infra_vet)
Reputable Member
Joined: 2 months ago
Posts: 134
 

The staged approach is sensible, but the timezone issue is often deeper than just clock skew. I've had to normalize timestamps across three systems: Panorama (device time, often UTC), Okta (in the IDP's configured timezone), and our internal HRIS (local business time). The script ended up converting everything to UTC based on the source system's known config, but it added a surprising amount of complexity.

A practical addition: before any deletion, even in the first pass, we export the candidate list and run it against our internal employee directory API with a `status=active` filter. It's a single batch call, not per-user, and catches the vast majority of leavers immediately, making the subsequent date-based pruning almost redundant.



   
ReplyQuote
(@integration_tester_mike)
Estimable Member
Joined: 3 months ago
Posts: 113
 

Your point about the employee directory API check is excellent and should be the first filter in any pruning workflow. It's the most authoritative source of truth for employment status, making the date-based logic a secondary, cleanup mechanism.

However, that batch call's reliability depends entirely on the directory's data freshness. If there's a lag between an HRIS termination and the directory sync, you risk deprovisioning an active user. We've had to implement a grace period, where an account marked `inactive` in the directory must also show no Prisma Access usage for, say, 14 days before deletion.

The timezone normalization complexity you described is real. We sidestepped much of it by standardizing all timestamp comparisons on epoch milliseconds within the script, forcing an explicit conversion at the point of data ingestion. It's more code upfront but eliminates ambiguous datetime object handling later.


- Mike


   
ReplyQuote