Developer Troubleshooting
Step-by-step procedures for every common Azure infrastructure task — with both an Azure Portal GUI walkthrough and a CLI/PowerShell command sequence. Use the tabs to switch between approaches.
Every procedure has a GUI tab (Azure / Entra portal) and a CLI tab (Azure CLI / PowerShell). A few operations are CLI only — the portal does not expose that capability. Your method preference is saved across tabs on this page.
Quick Reference
| Task | Section | Notes |
|---|---|---|
| Create Automation Account | Create AA | GUI or CLI |
| Enable Managed Identity | Managed Identity | GUI or CLI |
| Assign RBAC roles to MI | RBAC Roles | GUI or CLI |
| Create app registration (Entra) | App Registration | GUI or CLI |
| Upload cert to app registration | Upload Certificate | GUI or CLI |
| Grant Graph API permissions | Graph Permissions | GUI or CLI |
| Grant Exchange.ManageAsApp | Exchange.ManageAsApp | CLI only |
| Add SP to Exchange Admin role | Exchange Admin Role | CLI only |
| Create automation variables | AA Variables | GUI or CLI |
| Install AA module | AA Modules | GUI or CLI |
| Import / publish runbook | Runbooks | GUI or CLI |
| Create runbook webhook | Webhook | GUI or CLI |
| Set Function App settings | Function Settings | GUI or CLI |
| Deploy API zip | Deploy API | GUI or CLI |
| Upload / bind SSL cert | SSL Cert | GUI or CLI |
| Manage DNS records | DNS Records | GUI or CLI |
| Renew Let's Encrypt cert | Cert Renewal | CLI recommended |
Create an Automation Account
- In the Azure portal, search for Automation Accounts and open the service.
- Click + Create.
- Select Subscription: TATER Security (3d36e4d4-…).
- Select or create Resource Group:
rg-tater-prod. - Enter an Account name (e.g.
aa-tater-cb). - Choose Region: East US.
- Click Review + Create, then Create.
az account set --subscription 3d36e4d4-b9f8-470e-8cf9-95b860e2197c
az automation account create \
--name aa-tater-cb \
--resource-group rg-tater-prod \
--location eastus \
--sku Basic
Enable System-Assigned Managed Identity
- Open your Automation Account in the portal.
- In the left menu under Settings, click Identity.
- On the System assigned tab, set the Status toggle to On.
- Click Save, then confirm the prompt.
- The Object (principal) ID that appears is the Managed Identity ID — copy it for RBAC assignments.
az automation account update \
--name aa-tater-cb \
--resource-group rg-tater-prod \
--assign-identity '[system]'
# Get the principal ID for RBAC assignments
az automation account show \
--name aa-tater-cb \
--resource-group rg-tater-prod \
--query identity.principalId -o tsv
Assign RBAC Roles to the Managed Identity
The Automation Account's Managed Identity needs four role assignments:
- Automation Operator on its own Automation Account (for child runbook delegation)
- Key Vault Certificates Officer on
kv-tatersec-tater - Key Vault Secrets User on
kv-tatersec-tater - Storage Blob Data Reader on
sttatersectater
Repeat for each resource (Automation Account, Key Vault, Storage Account):
- Open the target resource in the portal.
- In the left menu click Access control (IAM).
- Click + Add → Add role assignment.
- On the Role tab, search for the role name and select it.
- Click Next. On the Members tab, set Assign access to = Managed identity.
- Click + Select members. In the panel, choose Managed identity type = Automation Account and select your account.
- Click Select → Review + assign → Review + assign.
MI_ID="<principalId from managed identity step>"
AA_ID=$(az automation account show --name aa-tater-cb --resource-group rg-tater-prod --query id -o tsv)
KV_ID=$(az keyvault show --name kv-tatersec-tater --resource-group rg-tater-prod --query id -o tsv)
SA_ID=$(az storage account show --name sttatersectater --resource-group rg-tater-prod --query id -o tsv)
# Automation Operator on the AA itself
az role assignment create --assignee-object-id $MI_ID --assignee-principal-type ServicePrincipal \
--role "Automation Operator" --scope $AA_ID
# Key Vault Certificates Officer
az role assignment create --assignee-object-id $MI_ID --assignee-principal-type ServicePrincipal \
--role "Key Vault Certificates Officer" --scope $KV_ID
# Key Vault Secrets User
az role assignment create --assignee-object-id $MI_ID --assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" --scope $KV_ID
# Storage Blob Data Reader
az role assignment create --assignee-object-id $MI_ID --assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Reader" --scope $SA_ID
Create an App Registration (Entra ID)
The app registration must be created in the client's Entra tenant, not the TATER Security tenant. Switch portal directory or set --tenant accordingly.
- Switch the portal to the client's directory (top-right avatar → Switch directory).
- Navigate to Microsoft Entra ID → App registrations → + New registration.
- Set Name: e.g.
TATER-CB-ComplianceScanner. - Set Supported account types: Accounts in this organizational directory only.
- Leave Redirect URI blank. Click Register.
- Copy the Application (client) ID and Directory (tenant) ID — you'll need both.
# Authenticate to the client's tenant first
az login --tenant <client-tenant-id>
az ad app create \
--display-name "TATER-CB-ComplianceScanner" \
--sign-in-audience AzureADMyOrg
# Get the app ID
APP_ID=$(az ad app list --display-name "TATER-CB-ComplianceScanner" --query "[0].appId" -o tsv)
echo "App ID: $APP_ID"
# Create the service principal
az ad sp create --id $APP_ID
SP_OID=$(az ad sp show --id $APP_ID --query id -o tsv)
echo "SP Object ID: $SP_OID"
Upload Certificate to App Registration
- Open the app registration in the Entra portal.
- Click Certificates & secrets in the left menu.
- On the Certificates tab, click Upload certificate.
- Select the
.cer(public key) file you generated with OpenSSL. Do not upload the PFX — the portal only accepts the public key here. - Add an optional description, then click Add.
- Copy the Thumbprint displayed.
Generate a self-signed cert with OpenSSL (macOS/Linux) or New-SelfSignedCertificate (Windows only). Store the PFX in Key Vault; upload only the .cer public key here.
# Generate cert with OpenSSL (works on macOS and Linux)
openssl req -x509 -nodes -days 1095 \
-newkey rsa:2048 \
-keyout tater-scanner.key \
-out tater-scanner.cer \
-subj "/CN=TATER-Scanner"
# Convert to PFX for Key Vault import
openssl pkcs12 -export \
-in tater-scanner.cer \
-inkey tater-scanner.key \
-out tater-scanner.pfx \
-passout pass:""
# Import PFX into Key Vault (TATER Security sub)
az account set --subscription 3d36e4d4-b9f8-470e-8cf9-95b860e2197c
az keyvault certificate import \
--vault-name kv-tatersec-tater \
--name tater-cb-scanner \
--file tater-scanner.pfx \
--password ""
# Upload public key to the app registration (client tenant)
az login --tenant <client-tenant-id>
az ad app credential reset \
--id $APP_ID \
--cert "@tater-scanner.cer" \
--append
Grant Graph API Permissions
- Open the app registration → API permissions.
- Click + Add a permission → Microsoft Graph → Application permissions.
- Search for and add each permission listed below.
- After adding all permissions, click Grant admin consent for [tenant] and confirm.
Required permissions:
- User.Read.All
- Group.Read.All
- Directory.Read.All
- Policy.Read.All
- Reports.Read.All
- AuditLog.Read.All
- RoleManagement.Read.Directory
- IdentityRiskyUser.Read.All
- UserAuthenticationMethod.Read.All
- Organization.Read.All
- DeviceManagementConfiguration.Read.All
- DeviceManagementManagedDevices.Read.All
- DeviceManagementServiceConfig.Read.All
The "Grant admin consent" button requires a Global Administrator or Privileged Role Administrator account in the client tenant.
# Get the Graph service principal ID in the client tenant
GRAPH_SP=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query id -o tsv)
# Map permission names to app role IDs (Graph)
declare -A PERMS=(
[User.Read.All]="df021288-bdef-4463-88db-98f22de89214"
[Group.Read.All]="5b567255-7703-4780-807c-7be8301ae99b"
[Directory.Read.All]="7ab1d382-f21e-4acd-a863-ba3e13f7da61"
[Policy.Read.All]="246dd0d5-5bd0-4def-940b-0421030a5b68"
[Reports.Read.All]="230c1aed-a721-4c5d-9cb4-a90514e508ef"
[AuditLog.Read.All]="b0afded3-3588-46d8-8b3d-9842eff778da"
[RoleManagement.Read.Directory]="483bed4a-2ad3-4361-a73b-c83ccdbdc53c"
[IdentityRiskyUser.Read.All]="dc5007c0-2d7d-4c42-879c-2dab87571379"
[UserAuthenticationMethod.Read.All]="38d9df27-64da-44fd-b7c5-a6fbac20248f"
[Organization.Read.All]="498476ce-e0fe-48b0-b801-37ba7ef9c1a2"
[DeviceManagementConfiguration.Read.All]="dc377aa6-52d8-4e23-b271-2a7ae04cedf3"
[DeviceManagementManagedDevices.Read.All]="2f51be20-0bb4-4fed-bf7b-db946066c75e"
[DeviceManagementServiceConfig.Read.All]="06a5fe6d-c49d-46a7-b082-56b1b14103c7"
)
for role_id in "${PERMS[@]}"; do
az rest --method POST \
--url "https://graph.microsoft.com/v1.0/servicePrincipals/$SP_OID/appRoleAssignments" \
--body "{\"principalId\":\"$SP_OID\",\"resourceId\":\"$GRAPH_SP\",\"appRoleId\":\"$role_id\"}"
done
Grant Exchange.ManageAsApp CLI only
Exchange.ManageAsApp is an app role on the Exchange Online service principal, not a Microsoft Graph permission. The Entra portal's "API permissions" UI does not list it. You must use the Graph API or PowerShell.
# Must be authenticated to the CLIENT's tenant
az login --tenant <client-tenant-id>
# Get a Graph API token for the client tenant
GRAPH_TOKEN=$(az account get-access-token \
--resource https://graph.microsoft.com/ \
--query accessToken -o tsv)
# Find the Exchange Online service principal in the client tenant
EXO_SP=$(curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
"https://graph.microsoft.com/v1.0/servicePrincipals?\$filter=appId eq '00000002-0000-0ff1-ce00-000000000000'" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['value'][0]['id'])")
# Exchange.ManageAsApp role ID (constant across all tenants)
ROLE_ID="dc50a0fb-09a3-484d-be87-e023b12c6440"
# Assign the role
curl -s -X POST \
-H "Authorization: Bearer $GRAPH_TOKEN" \
-H "Content-Type: application/json" \
"https://graph.microsoft.com/v1.0/servicePrincipals/$SP_OID/appRoleAssignments" \
-d "{\"principalId\":\"$SP_OID\",\"resourceId\":\"$EXO_SP\",\"appRoleId\":\"$ROLE_ID\"}"
echo "Done. Verify with: GET /servicePrincipals/$SP_OID/appRoleAssignments"
Add SP to Exchange Administrator Directory Role CLI only
The Entra portal allows adding users to the Exchange Administrator role but not service principals. Use the Graph API directly.
# Exchange Administrator role template ID (constant)
EXCH_ROLE_TEMPLATE="29232cdf-9323-42fd-ade2-1d097af3e4de"
# Activate the role (required if no members yet)
curl -s -X POST \
-H "Authorization: Bearer $GRAPH_TOKEN" \
-H "Content-Type: application/json" \
"https://graph.microsoft.com/v1.0/directoryRoles" \
-d "{\"roleTemplateId\":\"$EXCH_ROLE_TEMPLATE\"}"
# Get the activated role ID
ROLE_OBJ=$(curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
"https://graph.microsoft.com/v1.0/directoryRoles?\$filter=roleTemplateId eq '$EXCH_ROLE_TEMPLATE'" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['value'][0]['id'])")
# Add the service principal as a member
curl -s -X POST \
-H "Authorization: Bearer $GRAPH_TOKEN" \
-H "Content-Type: application/json" \
"https://graph.microsoft.com/v1.0/directoryRoles/$ROLE_OBJ/members/\$ref" \
-d "{\"@odata.id\":\"https://graph.microsoft.com/v1.0/directoryObjects/$SP_OID\"}"
# Verify (should return HTTP 204 on success)
echo "Expected 204 No Content"
Create Automation Account Variables
Ten variables are required. The table below shows each name and what to set it to.
| Variable | Value | Encrypted |
|---|---|---|
AppClientId | Client app registration ID | No |
KeyVaultName | kv-tatersec-tater | No |
CertName | Certificate name in Key Vault | No |
TenantDomain | e.g. contoso.onmicrosoft.com | No |
TenantId | Client Entra tenant ID (GUID) | No |
OrganizationId | TATER org ID | No |
ApiBaseUrl | https://api.tatersecurity.com | No |
StorageAccountName | sttaterprod | No |
StorageContainer | scripts | No |
ApiKey | TATER API key | Yes |
- Open the Automation Account → Shared Resources → Variables.
- Click + Add variable.
- Enter the Name and Value from the table above.
- Set Type to String.
- For the
ApiKeyvariable, enable Encrypted. - Click Create. Repeat for all 10 variables.
ApiKey indicating it is encrypted.The az automation variable create CLI command is marked experimental and fails silently. Use the Azure Management REST API instead.
AA="aa-tater-cb"
RG="rg-tater-prod"
SUB="3d36e4d4-b9f8-470e-8cf9-95b860e2197c"
az account set --subscription $SUB
TOKEN=$(az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv)
BASE="https://management.azure.com/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.Automation/automationAccounts/$AA/variables"
API="?api-version=2019-06-01"
# Helper function
upsert_var() {
local name=$1 value=$2 encrypted=${3:-false}
curl -s -X PUT "$BASE/$name$API" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$name\",\"properties\":{\"value\":\"\\\"$value\\\"\",\"isEncrypted\":$encrypted}}"
}
upsert_var "AppClientId" "<client-app-id>"
upsert_var "KeyVaultName" "kv-tatersec-tater"
upsert_var "CertName" "tater-cb-scanner"
upsert_var "TenantDomain" "contoso.onmicrosoft.com"
upsert_var "TenantId" "<client-tenant-id>"
upsert_var "OrganizationId" "<tater-org-id>"
upsert_var "ApiBaseUrl" "https://api.tatersecurity.com"
upsert_var "StorageAccountName" "sttaterprod"
upsert_var "StorageContainer" "scripts"
upsert_var "ApiKey" "<tater-api-key>" true
Install Modules in Azure Automation
Azure Automation does not install modules listed in runbook comments. Each module must be explicitly installed. Installation is async — wait ~10 minutes and check provisioningState before running scans.
Required modules: ExchangeOnlineManagement 3.4.0 and Az.Automation.
- Open the Automation Account → Shared Resources → Modules.
- Click + Add a module.
- Select Browse from gallery.
- Search for
ExchangeOnlineManagement. Click the result, then click Select. - Set Runtime version to 5.1, then click Import.
- Wait for Status to change from Importing to Available (~5-10 min).
- Repeat for
Az.Automation(runtime 5.1).
BASE_AA="https://management.azure.com/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.Automation/automationAccounts/$AA"
# Install ExchangeOnlineManagement 3.4.0
EXO_URI="https://www.powershellgallery.com/api/v2/package/ExchangeOnlineManagement/3.4.0"
curl -s -X PUT "$BASE_AA/modules/ExchangeOnlineManagement?api-version=2019-06-01" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"properties\":{\"contentLink\":{\"uri\":\"$EXO_URI\"}}}"
# Install Az.Automation (latest)
AZ_AUTO_URI="https://www.powershellgallery.com/api/v2/package/Az.Automation"
curl -s -X PUT "$BASE_AA/modules/Az.Automation?api-version=2019-06-01" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"properties\":{\"contentLink\":{\"uri\":\"$AZ_AUTO_URI\"}}}"
# Check status
curl -s "$BASE_AA/modules/ExchangeOnlineManagement?api-version=2019-06-01" \
-H "Authorization: Bearer $TOKEN" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['properties']['provisioningState'])"
Import and Publish a Runbook
The az automation runbook replace-content CLI command sometimes doesn't update the published version. For reliable updates (especially PS 7.2 runbooks), use the REST API draft+publish steps shown in the CLI tab.
Initial import:
- Open the Automation Account → Runbooks → + Create a runbook.
- Enter the Name (e.g.
Scan-M365Cloud). - Set Runbook type to PowerShell and Runtime version to 5.1.
- Click Create.
- In the editor, paste the runbook content (or use the toolbar's Import file option to upload the
.ps1file). - Click Save, then Publish.
Updating an existing runbook:
- Click the runbook name to open it.
- Click Edit to open the editor.
- Paste the new content or use Import file.
- Click Save, then Publish. Confirm the overwrite prompt.
RUNBOOK="Scan-M365Cloud"
PS_FILE="./Runbooks/$RUNBOOK.ps1"
# Step 1: Upload draft content (api-version 2015-10-31 works for content upload)
curl -s -X PUT \
"$BASE_AA/runbooks/$RUNBOOK/draft/content?api-version=2015-10-31" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: text/powershell" \
--data-binary "@$PS_FILE"
# Step 2: Publish the draft (must use api-version 2019-06-01 for publish)
curl -s -X POST \
"$BASE_AA/runbooks/$RUNBOOK/publish?api-version=2019-06-01" \
-H "Authorization: Bearer $TOKEN"
echo "Runbook $RUNBOOK published"
Create a Runbook Webhook
- Open the runbook (e.g.
Scan-M365Cloud). - In the left menu under Resources, click Webhooks.
- Click + Add Webhook → Create new webhook.
- Enter a Name (e.g.
CB-ScanTrigger). - Set Expires to a date 5 years out (e.g. 2031-01-01).
- Copy the URL now — it is only shown once.
- Click Create → OK.
- Store the URL in the org record: TATER → Organizations → [Org] → Scan Infrastructure → Webhook URL.
WEBHOOK_NAME="CB-ScanTrigger"
RUNBOOK_HREF="$BASE_AA/runbooks/Scan-M365Cloud"
EXPIRY="2031-01-01T00:00:00Z"
RESPONSE=$(curl -s -X PUT \
"$BASE_AA/webhooks/$WEBHOOK_NAME?api-version=2015-10-31" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"$WEBHOOK_NAME\",
\"properties\": {
\"isEnabled\": true,
\"expiryTime\": \"$EXPIRY\",
\"runbook\": {\"name\": \"Scan-M365Cloud\"},
\"uri\": \"\"
}
}")
# The URL is ONLY returned on creation — save it immediately
echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['properties']['uri'])"
Set Function App Environment Variables
- Open
func-tater-sec-apiin the portal. - In the left menu under Settings, click Environment variables.
- Click + Add to add a new setting, or click an existing setting name to edit it.
- Enter the Name and Value.
- Click Apply, then Confirm at the top of the page to save all changes. The app will restart.
az account set --subscription 3d36e4d4-b9f8-470e-8cf9-95b860e2197c
# Set one or more settings at once
az functionapp config appsettings set \
--name func-tater-sec-api \
--resource-group rg-tater-prod \
--settings \
SCAN_WEBHOOK_URL="https://..." \
ANTHROPIC_API_KEY="sk-ant-..." \
ADO_PAT="..."
# List current settings
az functionapp config appsettings list \
--name func-tater-sec-api \
--resource-group rg-tater-prod \
--output table
Deploy the API
Stale api/dist/node_modules/ left from old func azure functionapp publish runs causes all routes to return 404. Delete it before zipping.
- Build the zip locally first:
npm ci && npx tscin theapi/directory, then deleteapi/dist/node_modules/if it exists, then zip theapi/folder contents. - In the portal, open the Function App → Deployment Center.
- Click the FTPS credentials tab to get credentials, or use the Manual deploy option if available.
- Alternatively, drag the zip into the Kudu console at
https://func-tater-sec-api.scm.azurewebsites.net/ZipDeployUI.
The easiest path is .\Deploy-API.ps1 (PowerShell) or ./ci-deploy-api.sh (macOS/Linux). These scripts handle the build, cleanup, zip, and deploy steps automatically.
# PowerShell (Windows/macOS)
.\Deploy-API.ps1
# -- OR manually: --
cd api
npm ci
npx tsc
Remove-Item -Recurse -Force dist/node_modules -ErrorAction SilentlyContinue # critical step
Compress-Archive -Path ./* -DestinationPath ../deploy-api.zip -Force
az account set --subscription 3d36e4d4-b9f8-470e-8cf9-95b860e2197c
az functionapp deploy \
--name func-tater-sec-api \
--resource-group rg-tater-prod \
--src-path ../deploy-api.zip \
--type zip
# Verify (expect 401)
curl -o /dev/null -s -w "%{http_code}" https://api.tatersecurity.com/api/scans
Upload and Bind an SSL Certificate
- Open the Function App → Settings → Custom domains.
- Ensure the custom domain is already verified (listed in the table).
- Click the pencil icon on the domain row, then Update binding.
- Under Certificate source, choose Import from Key Vault or Upload certificate (.pfx).
- If uploading: select the
.pfxfile and enter the password. - Click Validate + add.
App Service Managed certificates require a server farm and are not available on Consumption plan Function Apps. Use Let's Encrypt via posh-acme with DNS-01 challenge (see Cert Renewal).
az account set --subscription 3d36e4d4-b9f8-470e-8cf9-95b860e2197c
# Upload the PFX
THUMBPRINT=$(az webapp config ssl upload \
--name func-tater-sec-api \
--resource-group rg-tater-prod \
--certificate-file ./cert.pfx \
--certificate-password "" \
--query thumbprint -o tsv)
echo "Thumbprint: $THUMBPRINT"
# Bind via SNI
az webapp config ssl bind \
--name func-tater-sec-api \
--resource-group rg-tater-prod \
--certificate-thumbprint $THUMBPRINT \
--ssl-type SNI
Manage DNS Records
The tatersecurity.com DNS zone lives in rg-tater-prod under subscription 3d36e4d4-…. Always switch to this subscription before DNS operations.
- Switch the portal subscription to TATER Security (3d36e4d4-…).
- Search for DNS zones and open
tatersecurity.com. - Click + Record set.
- Set the Name, Type (A, CNAME, TXT), and Value.
- Click OK.
Current DNS records:
| Name | Type | Value |
|---|---|---|
@ | A | 20.119.16.36 |
@ (asuid) | TXT | 639846ccdcefc7… |
www | CNAME | witty-pond-02e628b0f.7.azurestaticapps.net |
app | CNAME | black-water-01adb5f0f.7.azurestaticapps.net |
api | CNAME | func-tater-sec-api.azurewebsites.net |
az account set --subscription 3d36e4d4-b9f8-470e-8cf9-95b860e2197c
DNS_RG="rg-tater-prod"
ZONE="tatersecurity.com"
# Add a CNAME record
az network dns record-set cname set-record \
--resource-group $DNS_RG --zone-name $ZONE \
--record-set-name "api" \
--cname "func-tater-sec-api.azurewebsites.net"
# Add a TXT record (e.g. domain verification)
az network dns record-set txt add-record \
--resource-group $DNS_RG --zone-name $ZONE \
--record-set-name "@" \
--value "your-verification-token"
# Update an A record
az network dns record-set a add-record \
--resource-group $DNS_RG --zone-name $ZONE \
--record-set-name "@" \
--ipv4-address 20.119.16.36
# List all records
az network dns record-set list \
--resource-group $DNS_RG --zone-name $ZONE \
--output table
Renew the Let's Encrypt Certificate
The Let's Encrypt cert for tatersecurity.com expires 2026-07-08. Renew at least 2 weeks before this date. Issuer: Let's Encrypt R12. Thumbprint: 4F1FEDF8DE6CB25FED78887C4F9965FBD36B7315.
Posh-ACME generates the cert via DNS-01 challenge. After renewal, upload the new PFX through the portal:
- Run the posh-ACME renewal command (CLI tab) to generate the new cert files.
- Open
func-tater-sec-api→ Settings → Custom domains. - Click the pencil icon on
tatersecurity.com. - Under Certificate source, choose Upload certificate (.pfx).
- Select the new
.pfxfile from~/.local/share/posh-acme/acme-v02.../tatersecurity.com/(macOS/Linux) and enter the cert password (blank by default). - Click Validate + add.
# Use PowerShell 7+ with posh-acme installed
Import-Module Posh-ACME
# Must use TATER Security sub (where the DNS zone lives)
az account set --subscription '3d36e4d4-b9f8-470e-8cf9-95b860e2197c'
$t = az account get-access-token `
--resource https://management.azure.com/ `
--query accessToken -o tsv
# IMPORTANT: do NOT combine AZAccessToken with AZTenantId (ambiguous param set error)
Submit-Renewal -PluginArgs @{
AZSubscriptionId = '3d36e4d4-b9f8-470e-8cf9-95b860e2197c'
AZAccessToken = $t
}
# After renewal succeeds, re-upload and rebind
$pfxPath = (Get-PACertificate).PfxFile
$thumb = az webapp config ssl upload `
--name func-tater-sec-api `
--resource-group rg-tater-prod `
--certificate-file $pfxPath `
--certificate-password "" `
--query thumbprint -o tsv
az webapp config ssl bind `
--name func-tater-sec-api `
--resource-group rg-tater-prod `
--certificate-thumbprint $thumb `
--ssl-type SNI
echo "New thumbprint: $thumb"
Common Errors Quick Reference
TypeError: fetch failed (ENOTFOUND)
The webhook URL's DNS routing has gone stale. Delete the old webhook and create a new one (see Webhook section). Update SCAN_WEBHOOK_URL on the Function App or the org's scanWebhookUrl in Cosmos.
Check for api/dist/node_modules/ in the deployment zip (causes module duplication) and verify the function file is imported in api/src/index.ts.
The app registration was migrated to a new tenant but users in the old tenant haven't had the service principal created. Run az ad sp create --id 45afb90d-c5de-439d-8b09-8fbfed28b321 in the context of the user's tenant and grant consent.
The ExchangeOnlineManagement module is not installed in the Automation Account. Install it from the Modules gallery (runtime 5.1) and wait ~10 minutes for provisioning to complete.
az functionapp deploy fails with 409 Conflict
Remove the WEBSITE_RUN_FROM_PACKAGE app setting before using config-zip, or switch to az functionapp deploy --type zip which handles run-from-package correctly.
TATER