Microsoft Entra App Registration Credential Expiry Checker
Project description
Microsoft Entra App Registration Credential Expiry Checker
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
- Discovery: Reads all App Registrations from the authenticated Entra tenant
- Validation: Fetches App Registration details from Microsoft Graph API
- Checking: Examines secrets and certificates for each app
- Notification: Sends email alerts to the app owners (if set) + email configured in
DEFAULT_NOTIFICATION_EMAILenvironment variable (if set) - 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
- Azure Storage Account: You need an Azure Storage account with Table Storage enabled
- 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
- Discovery: Reads all entities from the specified Azure Table
- Validation: Fetches app registration details from Microsoft Graph API
- Checking: Examines secrets and certificates for each app
- Notification: Sends email alerts to the email addresses specified in PartitionKey
- 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
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a6e3131561ebd517d2fafa9ad4f393f2cc6bd411d467938c87ff382842555e6
|
|
| MD5 |
8185de776fea631258b2302f59a5d22a
|
|
| BLAKE2b-256 |
d4bdb2a51d95a2adc933b525878e1582728961f137e941729c0373e62d90b3c6
|
File details
Details for the file entra_expiry_checker-1.1.0-py3-none-any.whl.
File metadata
- Download URL: entra_expiry_checker-1.1.0-py3-none-any.whl
- Upload date:
- Size: 25.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
93802dbae03e95dccbebbd799b4d43ea2e968d100e8e69bb1771803fb8b6fc51
|
|
| MD5 |
bc6fac3feffc171453eaaa2efe108988
|
|
| BLAKE2b-256 |
9954ac09bba827f03d26f86a430e1631089d4e3b899d3b174a388f16c57c963a
|