Skip to content

Instantly share code, notes, and snippets.

@RajChowdhury240
Last active April 8, 2026 17:10
Show Gist options
  • Select an option

  • Save RajChowdhury240/49c780d30ac4b5af81784a84a39eefee to your computer and use it in GitHub Desktop.

Select an option

Save RajChowdhury240/49c780d30ac4b5af81784a84a39eefee to your computer and use it in GitHub Desktop.
https://excalidraw.com/#json=3Cp7T04J-sCvL1HMhJ3b1,tW8XiCaq7mdoo8KDn70KQQ
@RajChowdhury240

Copy link
Copy Markdown
Author

The three-phase pipeline works like this:
Phase 1 — Detection runs daily via an EventBridge cron rule that triggers a Lambda scanner. That scanner calls IAM APIs (ListUsers, GetAccessKeyLastUsed, ListUserTags) and cross-references CloudTrail LookupEvents to check for any activity — console logins, API calls, key usage — within the last 60 days. It writes state to a DynamoDB tracking table (user ARN, last activity date, days idle, notification count) and pushes metrics to CloudWatch.
Phase 2 — Notification fires when a stale user is detected. The scanner publishes to an SNS topic, which invokes a Notification Lambda. That Lambda reads the sso:owner tag from the IAM user, resolves it to an employee email (via your corporate directory or a mapping table), and sends alerts through Outlook (via Microsoft Graph API) and/or MS Teams (via incoming webhook). A grace period of 7–14 days is enforced — the DynamoDB table tracks when the first notification was sent.
Phase 3 — Remediation kicks in after the grace period expires with no activity. A Revocation Lambda checks whether the IAM user's access keys are SailPoint-managed (you can tag this as managed-by:sailpoint). If yes, it creates a ticket/workflow in SailPoint's API for key rotation or deletion. If no, it directly executes one of two options you're deciding between.

On your Option A vs Option B decision — here's the tradeoff:
Option A (deactivate access keys) is reversible and lower-risk. The keys still exist, so if an owner responds late saying "I still need this," you flip them back to Active in seconds. But it only blocks programmatic access — if the user has a console password or inline policies granting other access paths, those remain.
Option B (detach all IAM policies) is more thorough — it kills both programmatic and console-based permissions. But it's harder to reverse cleanly, especially if the user had a complex set of managed + inline policies. You'd need to snapshot the policy attachments before detaching (store in DynamoDB) so you can restore them.
My recommendation: do both in sequence. First deactivate all access keys (immediate, reversible). If no response after another 7 days, detach policies. This gives you a two-tier escalation.

Edge cases and problems we might hit:

  • The IAM GetAccessKeyLastUsed API only tracks key usage, not console logins
  • you must also check CloudTrail for ConsoleLogin events and any AssumeRole calls where this user is the source principal.
  • Some IAM users may have no access keys at all (console-only users), so key-last-used alone gives you a false "inactive" reading.
  • CloudTrail's LookupEvents only retains 90 days of management events by default. If you need to look further back or at data events, -you'll need to query CloudTrail Lake or Athena against your S3-stored trail logs — this adds latency and cost.
  • The sso:owner tag is your single point of failure for notification. If an IAM user is missing the tag, or the SSO value maps to an employee who's left the company, your notification goes nowhere. Build a fallback: if sso:owner is missing or unresolvable, escalate to a security team distribution list.

For the SailPoint dependency, the main risk is async latency — SailPoint workflows may take hours or days to complete. Your Lambda should create the ticket and then track its status in DynamoDB, with a separate reconciliation job that checks whether SailPoint actually completed the rotation. Don't assume fire-and-forget.
Service accounts are the biggest political problem. Many IAM users are machine identities used by CI/CD pipelines, legacy apps, or third-party integrations. These often show sporadic activity (monthly batch jobs, quarterly reports). Tag them as user-type:service-account and either exempt them or extend their threshold to 90–120 days. You'll also want an exception list in DynamoDB that security admins can manage.
For Microsoft Graph API (Outlook email), you'll need an Azure AD app registration with Mail.Send permissions and a client secret stored in Secrets Manager. For Teams, an incoming webhook URL per channel is simpler but less flexible. Graph API lets you send as a shared mailbox (e.g., security@yourcompany.com), which looks more professional and avoids replies going to a Lambda's non-existent inbox.

@RajChowdhury240

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment