Skip to main content

Microsoft Entra App Registration Credential Expiry Checker

Project description

Microsoft Entra App Registration Credential Expiry Checker

PyPI version Python versions License: MIT

A Python package for monitoring and alerting on expiring secrets and certificates in Microsoft Entra ID (formerly Azure AD) App Registrations. This tool helps you stay ahead of credential expiry issues by automatically checking your App Registrations and sending email notifications when secrets/certificates are nearing expiration.

Features

  • 🔍 Flexible Discovery: Check all App Registrations in your tenant or specify via Azure Table Storage
  • 📧 Email Notifications: Send alerts via SendGrid or SMTP when credentials are nearing expiration
  • 🔐 Secure Authentication: Uses Azure CLI or Managed Identity authentication for secure access
  • 📊 Detailed Reporting: Comprehensive logs and summary of findings
  • 🚀 Easy Deployment: Works locally, with GitHub Actions, Azure DevOps, or any CI/CD platform

Installation

From PyPI (Recommended)

pip install entra-expiry-checker

Quick Start

1. Set up Authentication

First, ensure you have the Azure CLI installed in your environment and that you are authenticated with Azure:

az login

Note: For Azure hosted or CI/CD deployments, Azure Managed Identity can also be used (where supported by the CI/CD platform).

Required Permissions

The identity being used (Azure CLI logged in user or Managed Identity) must have the ability to read Applications and Users from the directory. The following Microsoft Graph API permissions can be applied to an Managed Identity to achieve this.

  • Application.Read.All - Required to read App Registration details
  • User.ReadBasic.All - Required to read user information for app owners

2. Configure Environment Variables

Choose your email provider and set up the required variables:

SendGrid

export EMAIL_PROVIDER="sendgrid"
export SG_API_KEY="SG.your_sendgrid_api_key"
export FROM_EMAIL="noreply@yourdomain.com"

SMTP

export EMAIL_PROVIDER="smtp"
export SMTP_HOST="smtp.yourdomain.com"
export SMTP_PORT="587"
export SMTP_USER="your_smtp_username"
export SMTP_PASSWORD="your_smtp_password"
export FROM_EMAIL="noreply@yourdomain.com"

3. Run the Checker

entra-expiry-checker

Or run directly with Python:

python -m entra_expiry_checker.main

Configuration

Environment Variables

Variable Required Description Default
EMAIL_PROVIDER No Email provider (sendgrid or smtp) sendgrid
FROM_EMAIL Yes Sender email address -
MODE No Operation mode (tenant or storage) tenant
DAYS_THRESHOLD No Days before expiry to alert 30
VERIFY_SSL No Enable/disable SSL verification true

SendGrid Provider Variables

Variable Required Description Default
SG_API_KEY Yes* SendGrid API key -

*Required when EMAIL_PROVIDER=sendgrid

SMTP Provider Variables

Variable Required Description Default
SMTP_HOST Yes* SMTP server hostname -
SMTP_PORT No SMTP server port 587
SMTP_USER Yes* SMTP username -
SMTP_PASSWORD Yes* SMTP password -
SMTP_USE_TLS No Use STARTTLS true
SMTP_USE_SSL No Use SSL/TLS from the start false

*Required when EMAIL_PROVIDER=smtp

Tenant Mode Variables

Variable Required Description
DEFAULT_NOTIFICATION_EMAIL No Default email for apps without owners

Storage Mode Variables

Variable Required Description
STG_ACCT_NAME Yes Azure Storage account name
STG_ACCT_TABLE_NAME Yes Table name in storage account

Operation Modes

Tenant Mode

Checks all App Registrations in your Entra ID tenant.

How It Works
  1. Discovery: Reads all App Registrations from the authenticated Entra tenant
  2. Validation: Fetches App Registration details from Microsoft Graph API
  3. Checking: Examines secrets and certificates for each app
  4. Notification: Sends email alerts to the app owners (if set) + email configured in DEFAULT_NOTIFICATION_EMAIL environment variable (if set)
  5. Reporting: Provides summary of processed applications

Storage Mode

Reads onboarded App Registrations from Azure Table Storage. This mode is useful when you want to check specific App Registrations rather than all apps in your tenant.

Storage Mode Prerequisites
  1. Azure Storage Account: You need an Azure Storage account with Table Storage enabled
  2. Table Structure: Create a table with the following schema:
    • PartitionKey: Email address of the person or distribution list to notify
    • RowKey: Object ID of the app registration
Table Schema Example
PartitionKey RowKey
admin@company.com 12345678-1234-1234-1234-123456789012
dev@company.com 87654321-4321-4321-4321-210987654321
How It Works
  1. Discovery: Reads all entities from the specified Azure Table
  2. Validation: Fetches app registration details from Microsoft Graph API
  3. Checking: Examines secrets and certificates for each app
  4. Notification: Sends email alerts to the email addresses specified in PartitionKey
  5. Reporting: Provides summary of processed applications

Note: In Storage Mode, notifications are sent only to the email addresses specified in the Azure Table Storage (PartitionKey), not to the actual owners of the App Registrations. This allows you to control who receives notifications regardless of the app's ownership in Entra ID.

Benefits
  • Targeted Monitoring: Only check specific App Registrations
  • Flexible Notifications: Different people can be notified for different apps
  • Audit Trail: Track which apps are being monitored
  • Cost Effective: Avoid checking unnecessary applications

CI/CD Integration

GitHub Actions

Recommended Authentication: For GitHub Actions, it's recommended to use an Azure user-assigned managed identity with federated credentials. This provides secure, credential-free authentication without storing secrets. See Microsoft's guide for setup instructions.

name: Check App Registration Credential Expiry

on:
  schedule:
    # Run daily at 9 AM UTC
    - cron: '0 9 * * *'
  workflow_dispatch:  # Allow manual triggering

env:
  PYTHON_VERSION: '3.13'

permissions:
  id-token: write
  contents: read

jobs:
  check-credentials:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: ${{ env.PYTHON_VERSION }}

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install entra-expiry-checker

    - name: Azure Login
      uses: azure/login@v1
      with:
        client-id: ${{ secrets.AZURE_CLIENT_ID }}
        tenant-id: ${{ secrets.AZURE_TENANT_ID }}
        subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

    - name: Check credential expiry
      run: entra-expiry-checker
      env:
        # Email provider configuration
        EMAIL_PROVIDER: ${{ vars.EMAIL_PROVIDER || 'sendgrid' }}
        FROM_EMAIL: ${{ vars.FROM_EMAIL }}

        # SendGrid configuration (required if EMAIL_PROVIDER=sendgrid)
        SG_API_KEY: ${{ secrets.SG_API_KEY }}

        # SMTP configuration (required if EMAIL_PROVIDER=smtp)
        SMTP_HOST: ${{ vars.SMTP_HOST }}
        SMTP_PORT: ${{ vars.SMTP_PORT || '587' }}
        SMTP_USER: ${{ secrets.SMTP_USER }}
        SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
        SMTP_USE_TLS: ${{ vars.SMTP_USE_TLS || 'true' }}
        SMTP_USE_SSL: ${{ vars.SMTP_USE_SSL || 'false' }}

        # Operation mode
        MODE: ${{ vars.MODE || 'tenant' }}

        # Days threshold
        DAYS_THRESHOLD: ${{ vars.DAYS_THRESHOLD || '30' }}

        # Tenant mode configuration (optional if MODE=tenant)
        DEFAULT_NOTIFICATION_EMAIL: ${{ vars.DEFAULT_NOTIFICATION_EMAIL }}

        # Storage mode configuration (only needed if MODE=storage)
        STG_ACCT_NAME: ${{ vars.STG_ACCT_NAME }}
        STG_ACCT_TABLE_NAME: ${{ vars.STG_ACCT_TABLE_NAME }}

        # SSL verification (set to false to disable SSL certificate verification)
        VERIFY_SSL: ${{ vars.VERIFY_SSL || 'true' }}

    - name: Handle failure
      if: failure()
      run: |
        echo "❌ Credential expiry check failed!"
        echo "Check the logs above for details."
        # Could add additional notification here (Slack, Teams, etc.)

Azure DevOps

Recommended Authentication: For Azure DevOps, it's recommended to use workload identity federation with a user-assigned managed identity. This provides secure, credential-free authentication without storing secrets. See Microsoft's guide for setup instructions.

trigger: none  # No CI trigger - only scheduled runs

schedules:
- cron: "0 9 * * *"  # Daily at 9 AM UTC
  displayName: Check App Registration Credential Expiry
  branches:
    include:
    - main # Modify as appropriate
  always: true

pool:
  vmImage: 'ubuntu-latest'

variables:
  PYTHON_VERSION: '3.13'

stages:
- stage: CheckCredentials
  displayName: 'Check App Registration Credential Expiry'
  jobs:
  - job: CheckCredentials
    displayName: 'Check Credential Expiry'
    steps:
    - task: UsePythonVersion@0
      displayName: 'Set up Python'
      inputs:
        versionSpec: '$(PYTHON_VERSION)'
        addToPath: true

    - task: AzureCLI@2
      displayName: 'Login and Run Tooling'
      inputs:
        azureSubscription: '$(AZURE_SUBSCRIPTION)'  # Service connection name
        scriptType: 'bash'
        scriptLocation: 'inlineScript'
        inlineScript: |
          echo "Successfully authenticated with Azure"
          az account show

          # Install Python dependencies
          python -m pip install --upgrade pip
          pip install entra-expiry-checker

          # Run the credential check script
          entra-expiry-checker
      env:
        # Email provider configuration
        EMAIL_PROVIDER: $(EMAIL_PROVIDER)  # Default: sendgrid
        FROM_EMAIL: $(FROM_EMAIL)

        # SendGrid configuration (required if EMAIL_PROVIDER=sendgrid)
        SG_API_KEY: $(SG_API_KEY)

        # SMTP configuration (required if EMAIL_PROVIDER=smtp)
        SMTP_HOST: $(SMTP_HOST)
        SMTP_PORT: $(SMTP_PORT)  # Default: 587
        SMTP_USER: $(SMTP_USER)
        SMTP_PASSWORD: $(SMTP_PASSWORD)
        SMTP_USE_TLS: $(SMTP_USE_TLS)  # Default: true
        SMTP_USE_SSL: $(SMTP_USE_SSL)  # Default: false

        # Operation mode
        MODE: $(MODE)  # Default: tenant
        DAYS_THRESHOLD: $(DAYS_THRESHOLD)  # Default: 30
        VERIFY_SSL: $(VERIFY_SSL)  # Default: true

        # Tenant mode configuration (only needed if MODE=tenant)
        DEFAULT_NOTIFICATION_EMAIL: $(DEFAULT_NOTIFICATION_EMAIL)

        # Storage mode configuration (only needed if MODE=storage)
        STG_ACCT_NAME: $(STG_ACCT_NAME)
        STG_ACCT_TABLE_NAME: $(STG_ACCT_TABLE_NAME)

    - script: |
        echo "❌ Credential expiry check failed!"
        echo "Check the logs above for details."
        # Could add additional notification here (Slack, Teams, etc.)
      displayName: 'Handle failure'
      condition: failed()

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

Changelog

1.1.0 (2026-01-06)

  • Add support for SMTP + better tests + dep updates

1.0.2 (2025-06-06)

  • Fix incorrect secret/certificate count in email body

1.0.1 (2025-06-05)

  • Documentation updates

1.0.0 (2025-06-05)

  • Initial release
  • Support for tenant and storage modes
  • SendGrid email notifications
  • Azure CLI + Managed Identity authentication
  • Comprehensive configuration validation

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

entra_expiry_checker-1.1.0.tar.gz (34.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

entra_expiry_checker-1.1.0-py3-none-any.whl (25.4 kB view details)

Uploaded Python 3

File details

Details for the file entra_expiry_checker-1.1.0.tar.gz.

File metadata

  • Download URL: entra_expiry_checker-1.1.0.tar.gz
  • Upload date:
  • Size: 34.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for entra_expiry_checker-1.1.0.tar.gz
Algorithm Hash digest
SHA256 4a6e3131561ebd517d2fafa9ad4f393f2cc6bd411d467938c87ff382842555e6
MD5 8185de776fea631258b2302f59a5d22a
BLAKE2b-256 d4bdb2a51d95a2adc933b525878e1582728961f137e941729c0373e62d90b3c6

See more details on using hashes here.

File details

Details for the file entra_expiry_checker-1.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for entra_expiry_checker-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 93802dbae03e95dccbebbd799b4d43ea2e968d100e8e69bb1771803fb8b6fc51
MD5 bc6fac3feffc171453eaaa2efe108988
BLAKE2b-256 9954ac09bba827f03d26f86a430e1631089d4e3b899d3b174a388f16c57c963a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page