GitHub Actions Setup
Complete guide for configuring PIM Monitor on GitHub Actions. The scan workflow is defined in .github/workflows/scan.yml.
Initial Setup
Prerequisites
- GitHub repository with write access
- Azure AD tenant with PIM-enabled roles
- Service principal with appropriate Graph API permissions
- GitHub environment (optional, for approval gates)
1. Set Up OIDC Authentication
PIM Monitor uses OpenID Connect (OIDC) for token exchange instead of storing credentials.
In Azure AD:
- Register an app for your GitHub Actions workflow
- Configure federated credentials:
- Entity type:
GitHub Actions workflow - Organization:
joel-prins(your GitHub org) - Repository:
PIM-Monitor - Entity type: Environment (optional)
- Name:
production
- Entity type:
In GitHub:
- Go to Settings → Environments → New environment →
production - (Optional) Add required reviewers for approval gates
- Store Azure values as Secrets → Actions:
AZURE_CLIENT_ID = <app-id>
AZURE_TENANT_ID = <tenant-id>
AZURE_SUBSCRIPTION_ID = <subscription-id>
The workflow will use these to exchange the GitHub JWT for an Azure token.
2. Configure Secrets
Notification credentials (if using email or webhooks):
- Go to Settings → Secrets and variables → Actions
- Click New repository secret
| Secret Name | Value |
|---|---|
NOTIFICATION_EMAIL | [email protected] |
NOTIFICATION_MAIL_FROM | [email protected] |
NOTIFICATION_WEBHOOK_URL | https://hooks.slack.com/services/... |
3. Configure Variables (Non-secret)
- Go to Settings → Secrets and variables → Actions
- Click the Variables tab
- Click New repository variable
| Variable Name | Value |
|---|---|
NOTIFICATION_MIN_SEVERITY | Medium |
EXPIRING_WINDOW_DAYS | 14 |
REPORT_ARTIFACT | true |
Workflow Configuration
Understanding the Trigger
The workflow in .github/workflows/scan.yml is triggered by:
-
Schedule (default: every 6 hours)
schedule:- cron: '0 */6 * * *' -
Manual trigger (workflow_dispatch)
- Click Actions → PIM Change Scan → Run workflow in GitHub UI
Changing the Schedule
Edit .github/workflows/scan.yml:
on:
schedule:
- cron: '0 */6 * * *' # every 6 hours (default)
workflow_dispatch: # allow manual trigger
Common cron patterns:
0 * * * * = Every hour
0 */3 * * * = Every 3 hours
0 9 * * 1-5 = Weekdays at 9 AM UTC
0 0 * * 0 = Weekly (Sunday midnight UTC)
Job Configuration
The scan job has a few key settings:
jobs:
scan:
runs-on: ubuntu-latest
timeout-minutes: 30
environment: production # optional: requires approval
permissions:
id-token: write # required for OIDC
contents: write # required for git push
packages: read # for accessing artifacts
Important:
id-token: writeis required for OIDC token exchangecontents: writeis required to commit and push changestimeout-minutes: 30prevents long-running jobs; increase if needed
Module Caching
The workflow caches the Microsoft.Graph PowerShell module to speed up runs:
- name: Cache Microsoft.Graph module
uses: actions/cache@v4
with:
path: ~/.local/share/powershell/Modules/Microsoft.Graph
key: msgraph-${{ env.MSGRAPH_VERSION }}-${{ runner.os }}
To invalidate the cache: Change MSGRAPH_VERSION in .github/workflows/scan.yml.
Environment-Specific Configuration
Production Approval Gates
If you want approval before scans run in production:
- Create a GitHub environment called
production - Add required reviewers in Settings → Environments → production → Required reviewers
- The workflow will wait for approval before executing
jobs:
scan:
environment: production # adds approval requirement
Multiple Environments
If you have dev/staging/prod, create separate workflows or branch triggers:
name: PIM Scan - Production
on:
schedule:
- cron: '0 */6 * * *'
jobs:
scan:
if: github.ref == 'refs/heads/main'
# ... rest of job
Artifacts & Reports
Enabling HTML Report Artifact
Set REPORT_ARTIFACT=true in your repository variables.
The workflow will generate scan-report.html and attach it to the run:
- Go to Actions → PIM Change Scan → [latest run]
- Scroll down to Artifacts
- Download
scan-report.html
Accessing Artifacts Programmatically
# Download latest report from main branch
gh run download -n scan-report.html -D . \
-R owner/PIM-Monitor
Local Testing with act
Test your workflow locally before pushing:
Install act
# macOS
brew install act
# Linux
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
Set Up Secrets Locally
Create .secrets file in repo root:
AZURE_CLIENT_ID=<value>
AZURE_TENANT_ID=<value>
AZURE_SUBSCRIPTION_ID=<value>
NOTIFICATION_WEBHOOK_URL=https://...
Run Workflow Locally
# Run the scan workflow
act -j scan --secret-file .secrets
# View available jobs
act --list
# Run with debug output
act -j scan --secret-file .secrets --verbose
Note: OIDC token exchange may not work in local act environment; you may need to mock the token response.
Troubleshooting
Workflow not running on schedule
Check:
- Is the repository public? (GitHub free plan requires public repos for scheduled workflows)
- Are workflows enabled? Actions → check that workflows aren't disabled
- Branch default is
main? Schedule only runs on default branch
OIDC token exchange failing
Error: "Failed to acquire Graph API access token"
Solutions:
- Verify federated credential is configured in Azure AD (see Initial Setup step 1)
- Check
AZURE_CLIENT_ID,AZURE_TENANT_IDare correct secrets - Ensure app has Graph API permissions:
Directory.Read.All,RoleAssignmentSchedule.Read.Directory,Mail.Send(if email enabled) - If recently created, wait 5-10 minutes for credential sync
Notifications not sending
Check:
- Are
NOTIFICATION_EMAIL+NOTIFICATION_MAIL_FROMboth set? (both required) - Does the service principal have
Mail.Sendpermission? Check Azure AD app permissions - Is
NOTIFICATION_MIN_SEVERITYset to a valid value? (High, Medium, Low, Informational) - Check workflow run logs: Actions → [workflow] → scan → Scroll for warning messages
Module cache not working
Check:
- Is
MSGRAPH_VERSIONset correctly? (default: 2.35.1) - Changed version recently? Cache key includes version; old cache is invalid
- Is cache key path correct? Should be
~/.local/share/powershell/Modules/Microsoft.Graph
Artifacts not generated
Check:
- Is
REPORT_ARTIFACT=trueset in repository variables? - Were any changes detected? Reports only generate when changes exist
- Check workflow logs for permission errors on artifact staging
Compared to Azure DevOps
| Feature | GitHub Actions | Azure DevOps |
|---|---|---|
| Authentication | OIDC (built-in) | WIF/OIDC (via AzurePowerShell@5) |
| Secrets | Settings → Secrets | Pipeline → Variables |
| Artifacts | Built-in artifact storage | Artifact Storage |
| Environment | GitHub Environments | Deployment jobs |
| Approval Gates | Environment approval | Checks |
| Caching | actions/cache@v4 | Built-in pipeline caching |
Related Pages
- Environment Variables — All configurable variables
- Pipeline Configuration — Schedule, commit format, and Azure DevOps setup
- Email Notifications — Email setup
- Webhook Channels — Teams, Slack, Discord webhooks