DEV Community

Cover image for Solved: Automate Employee Offboarding: Revoke AWS & GitHub Access Script
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: Automate Employee Offboarding: Revoke AWS & GitHub Access Script

🚀 Executive Summary

TL;DR: The article presents a Python script to automate the manual, error-prone process of employee offboarding from AWS and GitHub. This solution efficiently revokes access by disabling AWS console logins, deleting access keys, and removing users from GitHub organizations, significantly enhancing security and saving time.

🎯 Key Takeaways

  • Automated offboarding for AWS involves deleting login profiles, revoking all access keys, detaching policies, and removing users from IAM groups, rather than immediate user deletion, to maintain audit trails.
  • Securely manage credentials using python-dotenv and a config.env file, ensuring sensitive AWS Access Key ID/Secret Access Key and GitHub Personal Access Token are not hardcoded and are excluded from source control.
  • GitHub access revocation is achieved via a DELETE request to the GitHub API’s organization members endpoint, requiring a Personal Access Token with admin:org scope.
  • The script requires specific AWS IAM permissions (e.g., iam:DeleteLoginProfile, iam:DeleteAccessKey, iam:RemoveUserFromGroup) and a GitHub PAT with admin:org scope for successful execution.
  • Robust error handling is included for scenarios like ‘No login profile found’ or ‘User not found in organization,’ making the script resilient to pre-existing states.

Automate Employee Offboarding: Revoke AWS & GitHub Access Script

Hey there, Darian Vance here. As a Senior DevOps Engineer at TechResolve, I’ve seen my fair share of urgent, late-night offboarding requests. For a long time, this was a manual, error-prone checklist: disable the AWS console password, hunt down and delete IAM keys, remove the user from the GitHub org… you know the drill. It was a time sink and, frankly, a security risk if a step was missed.

I finally decided to automate the whole process. This script I’m about to walk you through has been a game-changer. It turns a 30-minute, multi-tab headache into a 10-second command. It ensures consistency, speed, and peace of mind. Let’s get this set up so you can reclaim some of your valuable time.

Prerequisites

Before we dive in, make sure you have the following ready to go:

  • AWS IAM User Credentials: You’ll need an Access Key ID and Secret Access Key for an IAM user (or role) that has programmatic access. The user must have permissions to manage other IAM users. Specifically, you’ll need policies allowing actions like iam:DeleteLoginProfile, iam:RemoveUserFromGroup, iam:ListAccessKeys, iam:DeleteAccessKey, and iam:DetachUserPolicy.
  • GitHub Personal Access Token (PAT): This token needs to have the admin:org scope to be able to remove users from your organization.
  • Python 3: The script is written in Python, so you’ll need it installed on the machine where you plan to run this.
  • Required Python Libraries: You’ll need to install boto3 (the AWS SDK for Python), requests (for making HTTP requests to the GitHub API), and python-dotenv (for securely managing our credentials).

The Guide: Step-by-Step

Step 1: Setting Up Your Project

Alright, let’s get our workspace ready. I’ll skip the standard virtual environment setup commands since you likely have your own workflow for that. The important part is to create a project directory and install the necessary Python libraries. From your terminal, you would typically run the commands to install boto3, requests, and python-dotenv using pip.

Next, inside your project directory, create two files: offboard_script.py for our main logic, and a config.env file to store our secrets.

Step 2: Storing Credentials Securely in config.env

Hardcoding credentials directly in a script is a huge security no-go. We’ll use a config.env file to keep them separate. This file should be added to your .gitignore to ensure it never gets committed to source control.

Your config.env file should look like this:

# AWS Credentials
AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY
AWS_REGION=us-east-1

# GitHub Credentials
GITHUB_ORG_NAME=your-github-organization
GITHUB_PAT=your_personal_access_token
Enter fullscreen mode Exit fullscreen mode

Just replace the placeholder values with your actual credentials.

Step 3: The Revocation Logic

Now for the core of the script. We’ll build this in a few functions to keep things clean and readable. The main idea is to create one function for handling AWS and another for GitHub, then a main function to orchestrate the process.

Revoking AWS Access

For AWS, our goal isn’t to delete the user immediately but to completely lock them out. This preserves the user object for any future auditing needs. Our function will perform these actions in order:

  1. Delete the user’s login profile (disables console access).
  2. Find and delete all of the user’s access keys (disables CLI/API access).
  3. Detach all managed policies.
  4. Remove the user from any IAM groups.

Pro Tip: In my production setups, I don’t delete the IAM user for at least 90 days. Deactivating them this way is a security best practice. It prevents a new user from accidentally being created with the same name and inheriting permissions unexpectedly, and it gives you an audit trail.

Revoking GitHub Access

The GitHub part is more straightforward. We just need to make a single API call to remove the specified user from our organization. Error handling is key here to confirm the action was successful.

Pro Tip: Watch out for API rate limits. If you’re offboarding multiple users in a loop, you could hit GitHub’s limit. For a simple, one-off script like this, it’s not a major concern, but for larger-scale automation, you’d want to build in some checks and back-off logic.

Step 4: The Complete Script (offboard_script.py)

Here is the full Python script. I’ve added comments to explain each part of the logic, from loading credentials to executing the revocation steps.

import os
import sys
import boto3
import requests
from dotenv import load_dotenv

def revoke_aws_access(iam_client, username):
    """Locks an AWS IAM user out of their account completely."""
    print(f"--- Starting AWS Revocation for user: {username} ---")
    try:
        # 1. Delete login profile (disables console access)
        print("Step 1: Deleting console login profile...")
        try:
            iam_client.delete_login_profile(UserName=username)
            print(f"[SUCCESS] Deleted login profile for {username}.")
        except iam_client.exceptions.NoSuchEntityException:
            print(f"[INFO] No login profile found for {username}, skipping.")

        # 2. Deactivate and delete all access keys
        print("Step 2: Deleting all access keys...")
        paginator = iam_client.get_paginator('list_access_keys')
        for response in paginator.paginate(UserName=username):
            for key in response['AccessKeyMetadata']:
                print(f"  - Deleting access key {key['AccessKeyId']}...")
                iam_client.delete_access_key(UserName=username, AccessKeyId=key['AccessKeyId'])
        print("[SUCCESS] All access keys deleted.")

        # 3. Detach all user policies
        print("Step 3: Detaching all managed policies...")
        policies = iam_client.list_attached_user_policies(UserName=username)['AttachedPolicies']
        for policy in policies:
            print(f"  - Detaching policy {policy['PolicyArn']}...")
            iam_client.detach_user_policy(UserName=username, PolicyArn=policy['PolicyArn'])
        print("[SUCCESS] All managed policies detached.")

        # 4. Remove from all groups
        print("Step 4: Removing from all IAM groups...")
        groups = iam_client.list_groups_for_user(UserName=username)['Groups']
        for group in groups:
            print(f"  - Removing from group {group['GroupName']}...")
            iam_client.remove_user_from_group(UserName=group['GroupName'], UserName=username)
        print(f"[SUCCESS] Removed {username} from all groups.")

        print(f"--- AWS Revocation for {username} completed successfully. ---")
        return True

    except Exception as e:
        print(f"[ERROR] An unexpected error occurred during AWS revocation: {e}")
        return False

def revoke_github_access(github_org, github_pat, username):
    """Removes a user from the GitHub organization."""
    print(f"--- Starting GitHub Revocation for user: {username} ---")

    url = f"https://api.github.com/orgs/{github_org}/members/{username}"
    headers = {
        "Accept": "application/vnd.github.v3+json",
        "Authorization": f"token {github_pat}"
    }

    try:
        response = requests.delete(url, headers=headers)
        # A 204 No Content response means success
        if response.status_code == 204:
            print(f"[SUCCESS] Successfully removed {username} from the {github_org} GitHub org.")
            return True
        elif response.status_code == 404:
            print(f"[INFO] User {username} not found in the organization. They may have already been removed.")
            return True
        else:
            print(f"[ERROR] Failed to remove {username} from GitHub. Status: {response.status_code}, Response: {response.text}")
            return False

    except requests.exceptions.RequestException as e:
        print(f"[ERROR] A network error occurred while trying to revoke GitHub access: {e}")
        return False

def main():
    """Main function to orchestrate the offboarding process."""
    load_dotenv('config.env')

    # Load credentials from environment
    aws_key_id = os.getenv("AWS_ACCESS_KEY_ID")
    aws_secret_key = os.getenv("AWS_SECRET_ACCESS_KEY")
    aws_region = os.getenv("AWS_REGION")
    github_org = os.getenv("GITHUB_ORG_NAME")
    github_pat = os.getenv("GITHUB_PAT")

    if not all([aws_key_id, aws_secret_key, aws_region, github_org, github_pat]):
        print("[ERROR] Missing one or more credentials in config.env. Please check the file.")
        return

    # Get username from command-line arguments
    if len(sys.argv) < 2:
        print("Usage: python3 offboard_script.py <username>")
        return

    username_to_offboard = sys.argv[1]
    print(f"Starting offboarding process for: {username_to_offboard}\n")

    # Initialize AWS IAM client
    iam_client = boto3.client(
        'iam',
        aws_access_key_id=aws_key_id,
        aws_secret_access_key=aws_secret_key,
        region_name=aws_region
    )

    # Run revocation functions
    aws_success = revoke_aws_access(iam_client, username_to_offboard)
    print("\n") # Adding space for readability
    github_success = revoke_github_access(github_org, github_pat, username_to_offboard)

    print("\n--- Offboarding Summary ---")
    print(f"AWS Access Revocation: {'SUCCESS' if aws_success else 'FAILED'}")
    print(f"GitHub Access Revocation: {'SUCCESS' if github_success else 'FAILED'}")
    print("-------------------------")

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Step 5: Running the Script

To execute the offboarding process, you simply run the script from your terminal and pass the username as a command-line argument.

Example: python3 offboard_script.py jane.doe

The script will print its progress, letting you know exactly what it’s doing. For automation, you could trigger this script via an API call from your HR system or even a service ticket. For a scheduled cleanup, you might use a cron job. For instance, to run a script at 2 AM every Monday, the command would be something like: 0 2 * * 1 python3 script.py. Just be sure the script is adapted to get the username from a file or list in that case.

Common Pitfalls

Here are a few places where I’ve tripped up before, so you can avoid them:

  • Permissions, Permissions, Permissions: The most common error is an “Access Denied” message. 99% of the time, this means the IAM user running the script is missing a required permission, or the GitHub PAT doesn’t have the admin:org scope. Double-check them first.
  • Username Mismatches: This script assumes the AWS IAM username and the GitHub username are the same. In many organizations, they are not. In a more advanced setup, I use a mapping file (like a CSV or JSON) to link a person’s identity across different services.
  • User Not Found: The script handles cases where a user might already be removed from a service, but it’s good to be aware that a simple typo in the username will also result in a “not found” message. Always double-check the username you pass in.

Conclusion

And there you have it. With a single script, you’ve created a consistent, reliable, and secure process for offboarding employees from two critical systems. This isn’t just about saving time; it’s about reducing your company’s security exposure by ensuring access is revoked promptly and completely.

Feel free to expand on this. You could add logging to a file, send notifications to a Slack channel, or integrate it with other services like G Suite or Jira. This script is a solid foundation for a fully automated offboarding workflow.

Hope this helps streamline your operations. Happy automating!

– Darian Vance


Darian Vance

👉 Read the original article on TechResolve.blog


☕ Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

Top comments (0)