Automated infrastructure for creating Group Managed Service Accounts (gMSA) in Active Directory, triggered from Okta Workflows via Azure Automation webhooks.
- Automated gMSA Creation: Create gMSAs via webhook from Okta Workflows
- Secure Credential Management: Credentials stored in Azure Key Vault
- Infrastructure as Code: Complete Terraform configuration
- CI/CD Pipeline: GitHub Actions for automated deployment
- Comprehensive Logging: Azure Monitor integration for audit trails
- Idempotent Operations: Safe to run multiple times
- Azure subscription with appropriate permissions
- GitHub repository with Actions enabled
- Active Directory domain controller
- Domain-joined Windows Server for Hybrid Runbook Worker
- Okta Workflows license
┌─────────────────┐
│ Okta Workflow │
└────────┬────────┘
│ HTTPS POST
▼
┌─────────────────────────────────────────┐
│ Azure Automation │
│ ┌───────────────────────────────────┐ │
│ │ Webhook → Runbook (PowerShell) │ │
│ └──────────────┬────────────────────┘ │
│ │ │
│ ┌──────────────▼──────────────┐ │
│ │ Azure Key Vault │ │
│ │ - Domain Admin Credentials │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Hybrid Runbook Worker │
│ (On Domain-Joined Server) │
│ ┌───────────────────────────────────┐ │
│ │ Active Directory PowerShell │ │
│ │ New-ADServiceAccount │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Active Directory │
│ - Group Managed Service Account │
└─────────────────────────────────────────┘
.
├── .github/
│ └── workflows/
│ └── terraform-deploy.yml # GitHub Actions workflow
├── terraform/
│ ├── main.tf # Main Terraform configuration
│ ├── variables.tf # Variable definitions
│ ├── outputs.tf # Output definitions
│ ├── terraform.tfvars.example # Example variables file
│ └── runbooks/
│ └── Create-gMSA.ps1 # PowerShell runbook script
├── .gitignore
└── README.md
git clone https://github.com/your-org/azure-gmsa-automation.git
cd azure-gmsa-automationSee SETUP.md for detailed instructions.
Quick version:
# Create Terraform state storage
az group create --name rg-terraform-state --location eastus
az storage account create --name sttfstate$(openssl rand -hex 4) --resource-group rg-terraform-state --location eastus
az storage container create --name tfstate --account-name YOUR_STORAGE_ACCOUNTAdd these secrets to your GitHub repository:
AZURE_CLIENT_IDAZURE_TENANT_IDAZURE_SUBSCRIPTION_IDTF_STATE_RESOURCE_GROUPTF_STATE_STORAGE_ACCOUNTTF_STATE_CONTAINERDOMAIN_ADMIN_USERNAMEDOMAIN_ADMIN_PASSWORDDOMAIN_CONTROLLER
# Push to main branch to trigger deployment
git add .
git commit -m "Deploy infrastructure"
git push origin mainOn your domain-joined server:
# Follow instructions in Azure Portal:
# Automation Account > Hybrid worker groups > Add hybrid workerUse the webhook URI from GitHub Secret AZURE_WEBHOOK_URI in your Okta Workflow HTTP request.
Send a POST request to the webhook with this payload:
{
"AccountName": "gmsa-app-service",
"DNSHostName": "appserver.contoso.com",
"PrincipalsAllowedToRetrieve": ["APPSERVER01$", "APPSERVER02$"],
"Description": "Service account for application",
"ServicePrincipalNames": ["HTTP/appserver.contoso.com"],
"OrganizationalUnit": "OU=ServiceAccounts,DC=contoso,DC=com"
}| Parameter | Required | Description |
|---|---|---|
AccountName |
Yes | Name of the gMSA (without $) |
DNSHostName |
Yes | DNS host name for the account |
PrincipalsAllowedToRetrieve |
No | List of computers/users allowed to retrieve password |
Description |
No | Description of the account |
ServicePrincipalNames |
No | List of SPNs for the account |
OrganizationalUnit |
No | OU path where account should be created |
KdsRootKeyId |
No | Specific KDS Root Key to use |
Success:
{
"Status": "Success",
"AccountName": "gmsa-app-service",
"DNSHostName": "appserver.contoso.com",
"DistinguishedName": "CN=gmsa-app-service,OU=ServiceAccounts,DC=contoso,DC=com",
"SamAccountName": "gmsa-app-service$",
"ObjectGUID": "12345678-1234-1234-1234-123456789012",
"Created": "2025-09-30T10:30:00Z",
"Message": "gMSA created successfully",
"Timestamp": "2025-09-30 10:30:00"
}Error:
{
"Status": "Failed",
"AccountName": "gmsa-app-service",
"Error": "Access denied",
"ErrorDetails": "...",
"Timestamp": "2025-09-30 10:30:00"
}- Terraform >= 1.5.0
- Azure CLI
- Azure PowerShell module
cd terraform
# Copy and edit variables
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your values
# Initialize Terraform
terraform init \
-backend-config="resource_group_name=YOUR_RG" \
-backend-config="storage_account_name=YOUR_STORAGE" \
-backend-config="container_name=tfstate"
# Plan changes
terraform plan
# Apply changes (be careful!)
terraform apply- ✅ Credentials: Never stored in code, always in Key Vault
- ✅ Webhook URI: Sensitive value, stored in GitHub Secrets
- ✅ OIDC Authentication: Use federated credentials instead of client secrets
- ✅ Branch Protection: Require PR reviews for main branch
- ✅ Environment Protection: Require approvals for production deployments
- ✅ Audit Logging: All job executions logged to Azure Monitor
- ✅ Least Privilege: Service accounts have minimum required permissions
# Via Azure CLI
az automation job list \
--resource-group rg-okta-automation \
--automation-account-name aa-okta-gmsa
# Via Azure Portal
# Navigate to: Automation Account > Jobs// Failed jobs in last 24 hours
AutomationJobLogs
| where TimeGenerated > ago(24h)
| where ResultType == "Failed"
| project TimeGenerated, RunbookName, ErrorMessage
// Success rate by runbook
AutomationJobLogs
| where TimeGenerated > ago(7d)
| summarize
Total = count(),
Success = countif(ResultType == "Success"),
Failed = countif(ResultType == "Failed")
by RunbookName
| extend SuccessRate = round(100.0 * Success / Total, 2)Webhooks should be rotated every 6-12 months:
- Update
main.tfto create new webhook - Commit and push changes
- Update Okta Workflow with new URI
- Remove old webhook configuration
- Modify
terraform/runbooks/Create-gMSA.ps1 - Commit changes
- Push to main branch
- GitHub Actions will automatically deploy updates
# Update domain admin password in Key Vault
az keyvault secret set \
--vault-name YOUR_VAULT_NAME \
--name DomainAdminPassword \
--value "NEW_PASSWORD"Issue: Job stays in "Queued" status
- Solution: Check Hybrid Worker is online and registered
Issue: "Access Denied" errors
- Solution: Verify domain credentials and permissions in Active Directory
Issue: "Cannot find AD module"
- Solution: Install RSAT-AD-PowerShell on Hybrid Worker
Issue: Webhook returns 404
- Solution: Verify webhook hasn't expired, check runbook is published
See TROUBLESHOOTING.md for more details.
- Setup Guide - Complete setup instructions
- Troubleshooting - Common issues and solutions
- API Reference - Webhook API documentation
- Architecture - Detailed architecture overview
- Create a feature branch
- Make your changes
- Test thoroughly in non-production environment
- Submit a pull request
- Wait for review and approval
This project is licensed under the MIT License - see the LICENSE file for details.
For issues or questions:
- Check the Troubleshooting Guide
- Review GitHub Issues
- Contact the platform team
See CHANGELOG.md for version history and changes. *trigger initial push)
Maintainers: Your Platform Team
Last Updated: 2025-09-30