Operational Hygiene for the 40:1 Non-Human Identity Crisis
Just caught the news about the upcoming webinar on ghost identities, and honestly, the 68% statistic for 2024 cloud breaches tracks with what I'm seeing in the wild. It’s not always sophisticated phishing; it’s the API key that was created for a POC six months ago and never rotated.
The "40:1" ratio of non-human to human identities is terrifying. With AI agents and OAuth grants proliferating, the attack surface is exploding. We recently did an audit and found several service accounts with Owner roles that hadn't been authenticated in over a year.
Here is a quick Python script using the AWS SDK (boto3) I whipped up to identify IAM access keys older than 90 days. It's a good starting point for a cleanup sweep.
import boto3
from datetime import datetime, timedelta
client = boto3.client('iam')
threshold = datetime.now() - timedelta(days=90)
for user in client.list_users()['Users']:
username = user['UserName']
keys = client.list_access_keys(UserName=username)
for key in keys['AccessKeyMetadata']:
if key['CreateDate'].replace(tzinfo=None) < threshold:
print(f"Stale Key: {key['AccessKeyId']} for user {username}")
But managing these at scale is the real pain point. How are you all handling the lifecycle for temporary AI agent tokens? Do you treat them like human users or implement separate rotation policies?
We've moved to a strict 'Infrastructure as Code' (IaC) governance model. If a service account isn't defined in our Terraform state, it gets flagged for deletion. We use a scheduled Lambda function to compare the AWS API reality against our Terraform state file. Anything orphaned gets a tag for automatic deprovisioning after 7 days. It saved us during our last audit.
From a Blue Team perspective, we focus on the 'awakening' behavior. Orphaned keys aren't dangerous until they're used. We have a KQL rule in Sentinel looking for Service Principal sign-ins that haven't occurred in >90 days.
AADServicePrincipalSignIn
| where ResultType == 0
| where CreatedTime > ago(1d)
| join kind=anti (AADServicePrincipalSignIn
| where CreatedTime > ago(90d)
| distinct AppId
) on AppId
That "Owner" mention is a huge red flag. We tackled this by enforcing a strict 90-day expiration on new keys, but for the legacy cleanup, we run a quarterly script to identify Service Principals holding high-privilege directory roles. It catches the ones silently sitting with Global Admin or Owner rights.
Get-MgDirectoryRole | ForEach-Object {
Get-MgDirectoryRoleMember -DirectoryRoleId $_.Id | Where-Object { $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.servicePrincipal' }
}
Building on Alex's point, we restrict the where alongside the when. We apply Conditional Access policies to Service Principals, limiting sign-ins to trusted IP ranges or specific named locations. This effectively nullifies stolen keys used from external networks. To identify unmanaged principals, we use this PowerShell snippet to find those with active secrets but no assigned owners:
Get-MgServicePrincipal -All | Where-Object { $_.PasswordCredentials.Count -gt 0 -and $_.Owners.Count -eq 0 }
This helps prioritize cleanup.
Verified Access Required
To maintain the integrity of our intelligence feeds, only verified partners and security professionals can post replies.
Request Access