August 22, 2026
When I started building out my GRC Engineering portfolio, most of my compliance automation work was…
That made sense. AWS has a huge ecosystem for security and compliance, and projects like Security Hub and AWS Config gave me plenty to work…

By David ONeal
5 min read
That made sense. AWS has a huge ecosystem for security and compliance, and projects like Security Hub and AWS Config gave me plenty to work with.
But there was a problem.
A real environment doesn't live entirely in AWS.
Identity is one of the biggest pieces of the security and compliance puzzle, and for a lot of organizations, that identity layer lives somewhere like Okta.
That was the idea behind Project 9: build an automated compliance evidence collector for Okta.
GitHub: https://github.com/doneal78/grc-okta-collector
Why Okta?
One of the things I've noticed while looking at GRC Engineer job descriptions is how often Okta shows up.
At first, I thought of it mostly as an identity platform.
Then I started looking at it from a compliance perspective.
Now it makes a lot more sense.
For SOC 2, identity evidence is important.
Who has access?
Is MFA enabled?
What groups are users assigned to?
Who is authenticating?
Are there failed authentication attempts?
That information isn't sitting in AWS Security Hub.
It's sitting in the identity platform.
So if I'm trying to automate compliance evidence collection, stopping at AWS doesn't really make sense.
Project 9 was about extending the same evidence collection idea into identity.
The Old Way
The manual process isn't complicated.
Someone logs into Okta.
They export a list of users.
They check MFA enrollment.
They look at groups.
They pull authentication logs.
Then someone cleans everything up and puts it into a spreadsheet that can eventually become audit evidence.
It works.
It's also exactly the kind of repetitive work that computers are good at.
So I decided to automate it.
What I Built
The project ended up being pretty small, which I actually liked.
There are two main Python files, and each one has a specific job.
okta_client.py handles the HTTP side of things.
It stores the Okta base URL, builds the authorization headers, makes the requests, handles HTTP errors, and deals with pagination.
okta_collector.py handles the compliance side.
It calls the client, collects the data I need, converts the results into pandas DataFrames, and generates the final Excel workbook.
That separation turned out to be an important design decision.
I didn't want HTTP logic, API authentication, pagination, and compliance logic all mixed together in one giant file.
The client talks to Okta.
The collector understands what the data means from a compliance perspective.
That makes the code easier to maintain and gives me a cleaner place to deal with API changes later.
The Evidence I Collected
The collector pulls four main categories of evidence.
CC6.1 — User Access
The collector builds a complete user roster with information such as:
- Login
- Account status
- Last login
The point here is to establish the population of users with access to the environment.
That's much more useful for an auditor than a screenshot of a user list.
CC6.6 — MFA
This was probably the most important part of the project.
The collector checks each user's enrolled authentication factors and determines whether MFA is present.
The output gives me a simple result:
PASS or FAIL
That makes it much easier to turn identity data into something that can actually support a compliance assessment.
CC6.3 — Groups
The collector also pulls Okta groups and their descriptions.
That provides visibility into the organization's role-based access structure and supports the control around logical access and role assignment.
CC7.2 — Authentication Logs
Finally, the collector pulls authentication events.
That gives me visibility into:
- Who authenticated
- When they authenticated
- Whether authentication succeeded
- Why an authentication attempt failed
Again, this isn't just interesting data.
It's evidence.
And Then the Tool Found an Actual Problem
This was probably my favorite part of the project.
The collector found a real MFA gap in my test Okta tenant.
The MFA compliance score came back at 67%.
Two out of three users were enrolled.
One wasn't.
testuser1@davidoneal.dev came back with:
MFA = FALSE Status = FAIL
That's an actual CC6.6 finding.
And this is where the project became more interesting than simply writing an API script.
In a real compliance program, that finding wouldn't just sit in an Excel spreadsheet.
It would become a remediation item.
Someone would own it.
There would be a target remediation date.
The status would be tracked.
And eventually, the control would be tested again to confirm the issue was actually resolved.
That's the difference between collecting evidence and using evidence to manage risk.
Learning to Work With REST APIs
This project also forced me to work differently from my AWS projects.
With AWS, I could lean heavily on boto3.
Okta doesn't work that way here.
I was using Python's requests library to make the REST API calls directly.
That meant handling things like:
- Authorization headers
- HTTP status codes
- Response data
- Pagination
- API-specific errors
The basic pattern was straightforward, but it was a good reminder that every API has its own personality.
And apparently every API has its own way of making pagination interesting.
Pagination Was One of the Bigger Lessons
AWS has boto3 paginators that make pagination relatively painless.
Okta uses the HTTP Link response header.
The API basically tells you:
Here's the next page.
So the get_paginated method reads that header, follows the next URL, and keeps going until there isn't another page.
The concept is the same as what I had already done with AWS.
The implementation is different.
That distinction matters when you're building compliance automation across multiple platforms.
You can't assume every system is going to give you the same interface.
Then There Was the 429 Problem
While working with the Okta System Log API, I ran into rate limiting.
Okta returned a 429 Too Many Requests response.
Instead of letting the collector crash, I added handling for the condition and returned an empty DataFrame with a clear message.
That might sound like a small thing.
It's not.
A compliance collector that completely crashes because one API endpoint is temporarily rate limited isn't very useful.
It needs to fail in a way that's understandable to the person running it.
That also helped me think about API failures in categories.
A 401 means something different from a 404.
A 429 means something different from both.
The collector should know the difference.
Credentials Should Never Be in the Code
Another simple lesson that is easy to overlook when you're just trying to get something working.
I used python-dotenv to load credentials from environment variables.
The Okta token lives in a .env file.
The .env file is excluded through .gitignore.
The credentials don't belong in the source code.
It's a basic security practice, but these little decisions are exactly what separate a working prototype from something I'd actually be comfortable putting into a larger security engineering workflow.
What Project 9 Changed for Me
The biggest lesson wasn't really about Okta.
It was about perspective.
When I started this portfolio, I was thinking heavily about cloud compliance.
Then I started thinking about how the controls actually work across an organization.
AWS is one piece.
Identity is another.
Applications are another.
CI/CD is another.
Security tooling is another.
The compliance program has to connect all of those pieces.
That's where GRC Engineering starts becoming much more interesting to me.
I'm not just checking whether a control exists.
I'm thinking about where the evidence lives, how I can collect it, how often I should collect it, how I turn it into something useful, and what happens when the evidence shows a control isn't working.
That's a very different way of thinking about GRC than chasing screenshots before an audit.
The Bigger Lesson
Project 4 taught me how to automate evidence collection in AWS.
Project 9 taught me that the same idea needs to work outside of AWS.
And that's important because GRC doesn't stop at the cloud account.
Identity systems, SaaS platforms, applications, infrastructure, and security tools all contain pieces of the evidence an organization needs to understand its security posture.
The job of a GRC Engineer is increasingly about connecting those pieces.
That's the direction I'm trying to move toward.
Not just documenting controls.
Building systems that continuously prove they are working.