From 4b368070f6ceb5eb813757ab428e3d1ff30cf408 Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 11:28:45 -0700 Subject: [PATCH 01/13] docs: simplify README and move detailed setup into a guide Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 261 +++----------------------- TECHNICAL.md | 7 +- docs/FRONTDOOR.md | 2 +- docs/GETTING-STARTED.md | 246 ++++++++++++++++++++++++ docs/MULTI-PROVIDER-IMPLEMENTATION.md | 2 +- docs/ONBOARDING.md | 2 +- setup/docs/README.md | 2 +- setup/docs/Troubleshooting.md | 2 +- 8 files changed, 285 insertions(+), 239 deletions(-) create mode 100644 docs/GETTING-STARTED.md diff --git a/README.md b/README.md index 3345710..7474a7f 100644 --- a/README.md +++ b/README.md @@ -1,244 +1,43 @@ # External Phone Provider: Azure Function Sample -Connect Microsoft Entra ID to your phone provider to send one-time passwords by SMS or voice. -This External Phone Provider (EPP) sample sets up **one endpoint in one Azure region**. +Connect Microsoft Entra ID to your phone provider for SMS and voice one-time passwords. +Deploy **one endpoint in one Azure region**, using JavaScript, C#, or Python. +Guided setup handles Azure resources and code publication; no repository clone is needed. -You register an application, run a guided setup script, then connect and test your provider. -An administrator activates the endpoint only after those checks pass. You do not need to clone -this repository, build a package yourself, or follow all three language guides. +**Start in a dedicated nonproduction tenant and subscription.** This is a sample, not +production certification. Before deploying, [confirm supported provider access and Microsoft's +approved testing and activation procedure](docs/GETTING-STARTED.md#1-get-ready). +If either is unavailable, stop. Azure resources and provider services can incur charges. -The same Function endpoint supports both SMS and voice. Each request's `channel` selects its -configured provider URL, and the SMS and voice URLs can differ. Validate every channel you intend -to activate. +## Architecture -The screenshots show the Azure portal. You can also register the application -in the Microsoft Entra admin center. Identifying details are hidden; your portal layout may differ. +![Microsoft Entra ID sends an encrypted request through Easy Auth to a Function, which uses Key Vault and calls the phone provider](docs/images/single-region-architecture.png) -## 1. Get ready +*Easy Auth checks the Microsoft caller; the Function decrypts the request. +Live success means provider acceptance, not confirmed delivery; evaluation sends nothing. Region, credentials, caching, and +telemetry are illustrative: see [architecture details](TECHNICAL.md#single-region-architecture). +[Open the full-size diagram](docs/images/single-region-architecture.png).* -Start in a **dedicated nonproduction tenant and subscription**. +## Get started -Before purchasing or deploying, confirm access to a supported provider's EPP integration through -[Microsoft Security Store](https://securitystore.microsoft.com/private-solutions). Arrange -Microsoft's approved testing and activation procedure with your tenant administrator. -Buying an offer does not enable the tenant feature or deploy this sample. If access or the -approved procedure is unavailable, **stop here** and follow the -[access and support guidance](docs/ONBOARDING.md#before-purchasing-or-deploying). +Follow the **[five-step setup guide](docs/GETTING-STARTED.md)** in order: -In Security Store, open **Private solutions** and select your approved subscription to review -its available offers. See [sample offers](docs/ONBOARDING.md#before-purchasing-or-deploying) in the -detailed guide; a listing does not guarantee support in this setup script. +1. [Get ready](docs/GETTING-STARTED.md#1-get-ready): confirm access, approvals, permissions, tools, and budget. +2. [Register your application](docs/GETTING-STARTED.md#2-register-your-application): save the tenant and application client IDs. +3. [Run guided setup](docs/GETTING-STARTED.md#3-run-guided-setup): choose a provider and runtime, then review the deployment plan before approving. +4. [Connect and test](docs/GETTING-STARTED.md#4-connect-your-provider-and-test): configure provider authentication, then complete approved evaluation and live checks for each intended channel. +5. [Activate and hand over](docs/GETTING-STARTED.md#5-activate-and-hand-over): have the policy administrator activate only after validation; assign monitoring, renewal, and rollback owners. -Use the [SMS cost estimation guide](docs/COST-CALCULATOR.md) to create a planning estimate, -then confirm destination rates, fees, and final costs with your provider. +**Deployment does not activate policy. Keep Easy Auth enabled**; never bypass it to test. +See the [sample's production limitations](docs/CONTRACT.md#production-limitations). -Have these ready: + +## References -- **Provider account:** complete the provider's account and sender registration for your chosen - SMS or voice service. Check [guided provider support](docs/ONBOARDING.md#provider-credential-names). -- **Shell:** Azure Cloud Shell in PowerShell mode, or PowerShell 7+ with an Azure CLI user login. - Azure CLI 2.60.0+ is required for Flex Consumption (FC1), or 2.48.1+ for Premium (EP1). - Allow access to GitHub, Azure, Microsoft Graph, Key Vault, and the deployment endpoints. - C# also needs the .NET 8 SDK and NuGet access. -- **Administrators:** an Azure operator with subscription deployment, resource-provider registration, - and role-assignment permissions; an Entra Privileged Role Administrator for setup; and an - Authentication Policy Administrator for later activation. These are separate permissions. -- **Azure region and budget:** check availability and subscription quota for your chosen Linux - plan. Azure resources and provider services can incur charges. +- [Setup reference](setup/docs/README.md) and [troubleshooting](setup/docs/Troubleshooting.md) +- [Provider configuration and validation](docs/ONBOARDING.md) +- [Monitoring](docs/MONITORING.md), [Entra sign-in alerts](docs/SIGN-IN-MONITORING.md#recommended-monitors), and [SMS cost estimation](docs/COST-CALCULATOR.md) +- [Technical reference](TECHNICAL.md), [HTTP contract](docs/CONTRACT.md), and [BYOT integration](docs/BRING-YOUR-OWN-PROVIDER.md) -Review the [full prerequisites](setup/docs/README.md#prerequisites-for-step-2), including the -required Entra allowed-tenants preview, before running setup. Microsoft Graph operations reuse -the Azure CLI login; no Graph PowerShell SDK or separate Graph sign-in is required. The script -can install Bicep after confirmation; Azure CLI must already be installed. - -Choose an **onboarding owner** in your organization to coordinate the administrators, provider, -and Microsoft support. This is a sample, not production certification: it does not provide -automatic send retries, duplicate-send protection, or automatic certificate renewal. -Review the [production limitations](docs/CONTRACT.md#production-limitations) with that owner. - -## 2. Register your application - -In the customer tenant's **Microsoft Entra admin center > App registrations > New registration**: - -1. Give the application a recognizable name. -2. Select **Accounts in this organizational directory only**. Leave the redirect URI blank, - then select **Register**. Setup will make the app organizational multitenant and restrict - its allowed tenants after you approve the deployment plan. -3. On **Overview**, save the **Directory (tenant) ID** and **Application (client) ID** privately. - Use the client ID, not the Object ID. - -![App registration form with an example name, Single tenant only selected, and no redirect URI](docs/images/onboarding/app-registration.png) - -*Some portal versions label the account type **Single tenant only**. Choose your own directory. -The example above is an unsaved form, not an application you can reuse.* - -Use these field labels to find the right IDs: - -![App registration Overview with Application client ID and Directory tenant ID identified for setup and Object ID marked DO NOT USE](docs/images/onboarding/app-registration-overview.png) - -*Use **Application (client) ID** for `-ApplicationId` and **Directory (tenant) ID** for `-TenantId`. -The **Object ID** is not either of these setup inputs. All ID values are hidden in this example.* - -Do not add a client secret, API permission, or app role yourself; setup handles the remaining -configuration. See [application registration details](setup/docs/README.md#step-1---manually-create-the-application). - -Also keep your Azure subscription ID handy. The -[setup values worksheet](docs/ONBOARDING.md#inputs-you-supply-to-setup) explains where to find -each value. Use a new dedicated application and resource prefix for another independent endpoint; -changing the provider or region on a rerun does not create a separate deployment. - -## 3. Run guided setup - -Open **PowerShell 7** in a folder where you want to keep the script and deployment summary. -Download the script: - -```powershell -Invoke-WebRequest ` - -Uri 'https://raw.githubusercontent.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/main/setup/Setup-Epp.ps1' ` - -OutFile .\Setup-Epp.ps1 -``` - -Review the downloaded script, then run it: - -```powershell -.\Setup-Epp.ps1 -``` - -On a shared or multi-account workstation, use `.\Setup-Epp.ps1 -ForceAuthentication` to request -an explicit Azure CLI device-code sign-in. Microsoft Graph reuses that CLI session. - -Setup first lists cached Azure CLI user subscriptions. Select the customer tenant and subscription, -then select or enter the dedicated application client ID and choose an Azure region. Refresh or -sign in when the expected account is missing. - -Choose the provider route and enter or confirm the provider **by name**, then choose **one** -platform: Node.js (JavaScript), .NET (C#), or Python. Setup configures both SMS and voice URLs; -there is no channel prompt. It downloads and verifies the package, builds it if needed, and -deploys it for you. You do not need a separate ZIP upload or local settings file. - -Choose **FC1** for on-demand hosting that can scale to zero, or **EP1** for paid warm capacity. -FC1 can cold-start, and its free usage grant does not make the whole deployment free. -See [hosting plan details](setup/docs/README.md#service-plan-selection). -In **Tenant scope**, choose Global or EU for your provider route. This does not choose your -Azure region or guarantee data residency. -For the resource prefix, use 2-8 lowercase letters or digits, starting with a letter. - -Complete any required CLI sign-in, MFA, or administrator consent yourself. Setup checks -prerequisites and prepares the deployment plan; resolve any reported failure before continuing. - -For a new application, setup creates its encryption certificate. If encryption credentials -already exist, follow [certificate selection](setup/docs/README.md#encryption-certificate-selection) -to reuse one exact Key Vault certificate version or approve replacement. Coordinate maintenance -first: changing these credentials affects every endpoint sharing the application. - -**Review the complete deployment plan before typing `Yes`.** Check the accounts, region, -SMS and voice provider URLs, platform, plan, cache settings, resource names, permissions, and -the exact certificate key IDs to keep or remove and any access to an existing vault. Setup grants the -Microsoft phone-provider service principal Microsoft Graph `Application.Read.All`, a tenant-wide -application-read permission. `No` or Enter cancels deployment; sign-in and prerequisite prompts -are separate from this approval. - -**Want to review without deploying?** Run interactively and choose **No** at that final prompt. -This still performs online preflight and downloads. There is no `-DryRun` or `-WhatIf` switch; -see [plan review](setup/docs/README.md#reviewing-the-plan-without-deploying) for details. - -After you approve, leave the script running while it configures the application, creates the -Azure resources, and publishes the code. Wait for **Deployment completed** and the saved summary -before moving to Step 4. - -Setup creates the Function App, storage, Key Vault, identities, and monitoring resources. -**Do not create these resources manually first.** -Keep the public certificate and timestamped summary in `epp-output` beside the script. -Use the [summary field guide](setup/docs/README.md#read-the-deployment-summary) to find your -resources, endpoint URL, application client ID, and encryption key ID. No private key is downloaded. - -Setup verifies that the Function can resolve its selected private-key reference before changing -the registered encryption certificates. It also verifies **Easy Auth**, the endpoint's -caller-authentication protection, before opening public access. Never disable it to fix a setup -or testing problem. If setup fails, use the -[troubleshooting guide](setup/docs/Troubleshooting.md) rather than deleting resources and starting over. - -To find the deployed resources, open **Azure portal > Resource groups** and select the group -listed in your deployment summary. - -![Example regional resource group showing a Function App, monitoring, Key Vault, managed identity, hosting plan, and storage](docs/images/onboarding/resource-group.png) - -*This existing Premium test deployment shows the resource types to look for, not a new -deployment or a guarantee that your resource list will be identical. Use setup rather than -the portal's Create button to deploy the endpoint.* - -## 4. Connect your provider and test - -Complete the [provider authentication instructions](docs/ONBOARDING.md#complete-provider-authentication) -for your selected integration. This means either entering credentials in the setup-created Key Vault -or having the provider administrator authorize the application. Azure deployment alone does not -complete that step. Keep credentials out of source code, screenshots, and shared logs. - -For an API-key integration, follow the -[illustrated Key Vault steps](docs/ONBOARDING.md#where-to-enter-api-key-credentials-in-key-vault) -using your own vault and the exact secret names required by your provider. OAuth integrations -use the provider-administrator handoff instead; do not create API-key secrets for them. - -In your Function App, open **Settings > Authentication**. Check that authentication is -**Enabled**, access is set to **Require authentication**, and unauthenticated requests receive -**HTTP 401 Unauthorized**. These are checks, not instructions to change the approved caller. - -![Function Authentication page showing Enabled, Require authentication, and Return HTTP 401 Unauthorized](docs/images/onboarding/authentication.png) - -*The **Microsoft** identity provider on this page validates the caller. It is not the phone -provider you selected for SMS or voice.* - -Ask your onboarding owner to arrange the -[approved deployed-endpoint checks](docs/ONBOARDING.md#validate-the-deployed-endpoint): - -1. Confirm that missing or unauthorized caller credentials are rejected. -2. Run an authorized **evaluation** to check authentication and decryption without a provider send. -3. Run a separately approved **live test for each channel you intend to activate** and confirm - both provider acceptance and receipt of the SMS or voice call. Acceptance alone is not proof of delivery. -4. Check that the request and relevant logs appear in Application Insights. - -The [illustrated monitoring steps](docs/MONITORING.md#find-the-linked-resources-in-the-portal) -show where to open Application Insights and its Logs workspace. - -This repository does not supply a self-service token tool for the authorized Microsoft caller. -An ordinary Azure CLI token is not a substitute. If the approved test procedure is unavailable -or any check fails, **do not activate the endpoint**. Do not blindly retry a timed-out live send: -the provider may already have accepted it. - -## 5. Activate and hand over - -Have an **Authentication Policy Administrator** follow the -[supported activation procedure](setup/docs/README.md#step-3---manually-validate-and-activate-policy). -They must save the existing policy, apply the approved endpoint URL and application client ID, -preserve other settings, and read back and validate the change. **Setup does not activate policy.** -The EPP policy fields require Microsoft's assisted procedure; do not guess a Graph update. - -Repeat for each authentication method you intend to enable. SMS and voice can share the same -Function endpoint URL and application client ID; no additional setup run is needed for the second channel. - -Before handing over, confirm: - -- [ ] Provider authentication, authorized testing, and recipient delivery are verified. -- [ ] Policy is backed up, activated, and checked, with a [rollback plan](setup/docs/README.md#rollback-and-decommissioning). -- [ ] [Monitoring and alert notifications](docs/MONITORING.md) work for your chosen runtime. -- [ ] Owners are assigned for provider credentials, [certificate renewal](setup/docs/README.md#encryption-certificate-lifecycle), - support, and costs. - -Setup creates telemetry resources, **not alert rules or renewal notifications**. -Keep the deployment summary and validation evidence in restricted storage. -Deleting Azure resources does not undo an authentication policy change. - -## Find more detail - -| When you need to... | Read | -| --- | --- | -| Check permissions, prompts, hosting plans, or renewal | [Setup reference](setup/docs/README.md) | -| Find IDs, add provider credentials, or run acceptance checks | [Customer configuration and validation](docs/ONBOARDING.md) | -| Resolve a setup or delivery problem | [Troubleshooting](setup/docs/Troubleshooting.md) | -| Find logs and set up alerts | [Monitoring](docs/MONITORING.md) and [Application Insights](docs/APPLICATION-INSIGHTS.md) | -| Choose identity and configuration alerts | [Three recommended Entra monitors](docs/SIGN-IN-MONITORING.md#recommended-monitors), with sign-in investigation and alert setup | -| Understand the architecture, settings, or packages | [Technical reference](TECHNICAL.md) and [HTTP contract](docs/CONTRACT.md) | -| Explore multiple Azure regions | [Manual Front Door guide](docs/FRONTDOOR.md); not included in guided setup | -| Build a custom implementation | [JavaScript](javascript/README.md), [C#](dotnet/README.md), or [Python](python/README.md) | -| Implement the Entra ID-facing BYOT interface | [BYOT request and response guide](docs/BRING-YOUR-OWN-PROVIDER.md); caller authentication, encrypted payload, and responses, with customer-owned service integration | -| Design primary-to-secondary provider fallback | [Developer walkthrough](docs/MULTI-PROVIDER-IMPLEMENTATION.md); not a built-in feature | +Optional multi-region and custom-provider designs are in the [detailed reference index](docs/GETTING-STARTED.md#find-more-detail); +they are not built-in guided setup features. diff --git a/TECHNICAL.md b/TECHNICAL.md index 1dcab2a..3485409 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -1,7 +1,7 @@ # External Phone Provider: Technical Reference For the single-region customer deployment flow, start with the -[main onboarding guide](README.md). +[five-step setup guide](docs/GETTING-STARTED.md). Azure Front Door is an [optional manual multi-region design](docs/FRONTDOOR.md), not a feature enabled by the single-region setup script. Its additional readiness endpoint is not shipped in @@ -13,7 +13,7 @@ and managed-identity access to credentials. ## Single-region architecture -![Single-region External Phone Provider architecture](docs/images/single-region-architecture.png) +See the [single-region diagram in the sample overview](README.md#architecture). Microsoft Entra ID sends an authenticated, encrypted request. Easy Auth checks the caller, then the Function decrypts the request using its Key Vault-backed key. For a live request, the @@ -341,7 +341,8 @@ authentication; [separate deployed security checks](docs/ONBOARDING.md#4-package ## Docs -- **[README.md](README.md)**: single-region customer onboarding. +- **[README.md](README.md)**: sample overview and architecture. +- **[Setup guide](docs/GETTING-STARTED.md)**: single-region customer onboarding. - **[docs/ONBOARDING.md](docs/ONBOARDING.md)**: customer value sources, provider handoffs, validation, and optional manual deployment. - **[docs/FRONTDOOR.md](docs/FRONTDOOR.md)**: optional manual multi-region onboarding and observed failover limitations. - **[docs/CONTRACT.md](docs/CONTRACT.md)**: the language-agnostic contract every implementation follows. diff --git a/docs/FRONTDOOR.md b/docs/FRONTDOOR.md index f6020ab..817aeaa 100644 --- a/docs/FRONTDOOR.md +++ b/docs/FRONTDOOR.md @@ -2,7 +2,7 @@ Use Azure Front Door when you want **one public EPP URL backed by Function Apps in multiple regions**. You configure this option manually. For a single region, start with the -[main onboarding guide](../README.md). +[five-step setup guide](GETTING-STARTED.md). **We don't provide a Front Door deployment script.** The guided setup creates one regional endpoint, not the multi-region setup described here. This guide covers what you need to configure and test. diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md new file mode 100644 index 0000000..6c0a8e7 --- /dev/null +++ b/docs/GETTING-STARTED.md @@ -0,0 +1,246 @@ +# Set up an External Phone Provider endpoint + +[Sample overview and architecture](../README.md) | [Setup reference](../setup/docs/README.md) + +Connect Microsoft Entra ID to your phone provider to send one-time passwords by SMS or voice. +This External Phone Provider (EPP) sample sets up **one endpoint in one Azure region**. + +You register an application, run a guided setup script, then connect and test your provider. +An administrator activates the endpoint only after those checks pass. You do not need to clone +this repository, build a package yourself, or follow all three language guides. + +The same Function endpoint supports both SMS and voice. Each request's `channel` selects its +configured provider URL, and the SMS and voice URLs can differ. Validate every channel you intend +to activate. + +The screenshots show the Azure portal. You can also register the application +in the Microsoft Entra admin center. Identifying details are hidden; your portal layout may differ. + +## 1. Get ready + +Start in a **dedicated nonproduction tenant and subscription**. + +Before purchasing or deploying, confirm access to a supported provider's EPP integration through +[Microsoft Security Store](https://securitystore.microsoft.com/private-solutions). Arrange +Microsoft's approved testing and activation procedure with your tenant administrator. +Buying an offer does not enable the tenant feature or deploy this sample. If access or the +approved procedure is unavailable, **stop here** and follow the +[access and support guidance](ONBOARDING.md#before-purchasing-or-deploying). + +In Security Store, open **Private solutions** and select your approved subscription to review +its available offers. See [sample offers](ONBOARDING.md#before-purchasing-or-deploying) in the +detailed guide; a listing does not guarantee support in this setup script. + +Use the [SMS cost estimation guide](COST-CALCULATOR.md) to create a planning estimate, +then confirm destination rates, fees, and final costs with your provider. + +Have these ready: + +- **Provider account:** complete the provider's account and sender registration for your chosen + SMS or voice service. Check [guided provider support](ONBOARDING.md#provider-credential-names). +- **Shell:** Azure Cloud Shell in PowerShell mode, or PowerShell 7+ with an Azure CLI user login. + Azure CLI 2.60.0+ is required for Flex Consumption (FC1), or 2.48.1+ for Premium (EP1). + Allow access to GitHub, Azure, Microsoft Graph, Key Vault, and the deployment endpoints. + C# also needs the .NET 8 SDK and NuGet access. +- **Administrators:** an Azure operator with subscription deployment, resource-provider registration, + and role-assignment permissions; an Entra Privileged Role Administrator for setup; and an + Authentication Policy Administrator for later activation. These are separate permissions. +- **Azure region and budget:** check availability and subscription quota for your chosen Linux + plan. Azure resources and provider services can incur charges. + +Review the [full prerequisites](../setup/docs/README.md#prerequisites-for-step-2), including the +required Entra allowed-tenants preview, before running setup. Microsoft Graph operations reuse +the Azure CLI login; no Graph PowerShell SDK or separate Graph sign-in is required. The script +can install Bicep after confirmation; Azure CLI must already be installed. + +Choose an **onboarding owner** in your organization to coordinate the administrators, provider, +and Microsoft support. This is a sample, not production certification: it does not provide +automatic send retries, duplicate-send protection, or automatic certificate renewal. +Review the [production limitations](CONTRACT.md#production-limitations) with that owner. + +## 2. Register your application + +In the customer tenant's **Microsoft Entra admin center > App registrations > New registration**: + +1. Give the application a recognizable name. +2. Select **Accounts in this organizational directory only**. Leave the redirect URI blank, + then select **Register**. Setup will make the app organizational multitenant and restrict + its allowed tenants after you approve the deployment plan. +3. On **Overview**, save the **Directory (tenant) ID** and **Application (client) ID** privately. + Use the client ID, not the Object ID. + +![App registration form with an example name, Single tenant only selected, and no redirect URI](images/onboarding/app-registration.png) + +*Some portal versions label the account type **Single tenant only**. Choose your own directory. +The example above is an unsaved form, not an application you can reuse.* + +Use these field labels to find the right IDs: + +![App registration Overview with Application client ID and Directory tenant ID identified for setup and Object ID marked DO NOT USE](images/onboarding/app-registration-overview.png) + +*Use **Application (client) ID** for `-ApplicationId` and **Directory (tenant) ID** for `-TenantId`. +The **Object ID** is not either of these setup inputs. All ID values are hidden in this example.* + +Do not add a client secret, API permission, or app role yourself; setup handles the remaining +configuration. See [application registration details](../setup/docs/README.md#step-1---manually-create-the-application). + +Also keep your Azure subscription ID handy. The +[setup values worksheet](ONBOARDING.md#inputs-you-supply-to-setup) explains where to find +each value. Use a new dedicated application and resource prefix for another independent endpoint; +changing the provider or region on a rerun does not create a separate deployment. + +## 3. Run guided setup + +Open **PowerShell 7** in a folder where you want to keep the script and deployment summary. +Download the script: + +```powershell +Invoke-WebRequest ` + -Uri 'https://raw.githubusercontent.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/main/setup/Setup-Epp.ps1' ` + -OutFile .\Setup-Epp.ps1 +``` + +Review the downloaded script, then run it: + +```powershell +.\Setup-Epp.ps1 +``` + +On a shared or multi-account workstation, use `.\Setup-Epp.ps1 -ForceAuthentication` to request +an explicit Azure CLI device-code sign-in. Microsoft Graph reuses that CLI session. + +Setup first lists cached Azure CLI user subscriptions. Select the customer tenant and subscription, +then select or enter the dedicated application client ID and choose an Azure region. Refresh or +sign in when the expected account is missing. + +Choose the provider route and enter or confirm the provider **by name**, then choose **one** +platform: Node.js (JavaScript), .NET (C#), or Python. Setup configures both SMS and voice URLs; +there is no channel prompt. It downloads and verifies the package, builds it if needed, and +deploys it for you. You do not need a separate ZIP upload or local settings file. + +Choose **FC1** for on-demand hosting that can scale to zero, or **EP1** for paid warm capacity. +FC1 can cold-start, and its free usage grant does not make the whole deployment free. +See [hosting plan details](../setup/docs/README.md#service-plan-selection). +In **Tenant scope**, choose Global or EU for your provider route. This does not choose your +Azure region or guarantee data residency. +For the resource prefix, use 2-8 lowercase letters or digits, starting with a letter. + +Complete any required CLI sign-in, MFA, or administrator consent yourself. Setup checks +prerequisites and prepares the deployment plan; resolve any reported failure before continuing. + +For a new application, setup creates its encryption certificate. If encryption credentials +already exist, follow [certificate selection](../setup/docs/README.md#encryption-certificate-selection) +to reuse one exact Key Vault certificate version or approve replacement. Coordinate maintenance +first: changing these credentials affects every endpoint sharing the application. + +**Review the complete deployment plan before typing `Yes`.** Check the accounts, region, +SMS and voice provider URLs, platform, plan, cache settings, resource names, permissions, and +the exact certificate key IDs to keep or remove and any access to an existing vault. Setup grants the +Microsoft phone-provider service principal Microsoft Graph `Application.Read.All`, a tenant-wide +application-read permission. `No` or Enter cancels deployment; sign-in and prerequisite prompts +are separate from this approval. + +**Want to review without deploying?** Run interactively and choose **No** at that final prompt. +This still performs online preflight and downloads. There is no `-DryRun` or `-WhatIf` switch; +see [plan review](../setup/docs/README.md#reviewing-the-plan-without-deploying) for details. + +After you approve, leave the script running while it configures the application, creates the +Azure resources, and publishes the code. Wait for **Deployment completed** and the saved summary +before moving to Step 4. + +Setup creates the Function App, storage, Key Vault, identities, and monitoring resources. +**Do not create these resources manually first.** +Keep the public certificate and timestamped summary in `epp-output` beside the script. +Use the [summary field guide](../setup/docs/README.md#read-the-deployment-summary) to find your +resources, endpoint URL, application client ID, and encryption key ID. No private key is downloaded. + +Setup verifies that the Function can resolve its selected private-key reference before changing +the registered encryption certificates. It also verifies **Easy Auth**, the endpoint's +caller-authentication protection, before opening public access. Never disable it to fix a setup +or testing problem. If setup fails, use the +[troubleshooting guide](../setup/docs/Troubleshooting.md) rather than deleting resources and starting over. + +To find the deployed resources, open **Azure portal > Resource groups** and select the group +listed in your deployment summary. + +![Example regional resource group showing a Function App, monitoring, Key Vault, managed identity, hosting plan, and storage](images/onboarding/resource-group.png) + +*This existing Premium test deployment shows the resource types to look for, not a new +deployment or a guarantee that your resource list will be identical. Use setup rather than +the portal's Create button to deploy the endpoint.* + +## 4. Connect your provider and test + +Complete the [provider authentication instructions](ONBOARDING.md#complete-provider-authentication) +for your selected integration. This means either entering credentials in the setup-created Key Vault +or having the provider administrator authorize the application. Azure deployment alone does not +complete that step. Keep credentials out of source code, screenshots, and shared logs. + +For an API-key integration, follow the +[illustrated Key Vault steps](ONBOARDING.md#where-to-enter-api-key-credentials-in-key-vault) +using your own vault and the exact secret names required by your provider. OAuth integrations +use the provider-administrator handoff instead; do not create API-key secrets for them. + +In your Function App, open **Settings > Authentication**. Check that authentication is +**Enabled**, access is set to **Require authentication**, and unauthenticated requests receive +**HTTP 401 Unauthorized**. These are checks, not instructions to change the approved caller. + +![Function Authentication page showing Enabled, Require authentication, and Return HTTP 401 Unauthorized](images/onboarding/authentication.png) + +*The **Microsoft** identity provider on this page validates the caller. It is not the phone +provider you selected for SMS or voice.* + +Ask your onboarding owner to arrange the +[approved deployed-endpoint checks](ONBOARDING.md#validate-the-deployed-endpoint): + +1. Confirm that missing or unauthorized caller credentials are rejected. +2. Run an authorized **evaluation** to check authentication and decryption without a provider send. +3. Run a separately approved **live test for each channel you intend to activate** and confirm + both provider acceptance and receipt of the SMS or voice call. Acceptance alone is not proof of delivery. +4. Check that the request and relevant logs appear in Application Insights. + +The [illustrated monitoring steps](MONITORING.md#find-the-linked-resources-in-the-portal) +show where to open Application Insights and its Logs workspace. + +This repository does not supply a self-service token tool for the authorized Microsoft caller. +An ordinary Azure CLI token is not a substitute. If the approved test procedure is unavailable +or any check fails, **do not activate the endpoint**. Do not blindly retry a timed-out live send: +the provider may already have accepted it. + +## 5. Activate and hand over + +Have an **Authentication Policy Administrator** follow the +[supported activation procedure](../setup/docs/README.md#step-3---manually-validate-and-activate-policy). +They must save the existing policy, apply the approved endpoint URL and application client ID, +preserve other settings, and read back and validate the change. **Setup does not activate policy.** +The EPP policy fields require Microsoft's assisted procedure; do not guess a Graph update. + +Repeat for each authentication method you intend to enable. SMS and voice can share the same +Function endpoint URL and application client ID; no additional setup run is needed for the second channel. + +Before handing over, confirm: + +- [ ] Provider authentication, authorized testing, and recipient delivery are verified. +- [ ] Policy is backed up, activated, and checked, with a [rollback plan](../setup/docs/README.md#rollback-and-decommissioning). +- [ ] [Monitoring and alert notifications](MONITORING.md) work for your chosen runtime. +- [ ] Owners are assigned for provider credentials, [certificate renewal](../setup/docs/README.md#encryption-certificate-lifecycle), + support, and costs. + +Setup creates telemetry resources, **not alert rules or renewal notifications**. +Keep the deployment summary and validation evidence in restricted storage. +Deleting Azure resources does not undo an authentication policy change. + +## Find more detail + +| When you need to... | Read | +| --- | --- | +| Check permissions, prompts, hosting plans, or renewal | [Setup reference](../setup/docs/README.md) | +| Find IDs, add provider credentials, or run acceptance checks | [Customer configuration and validation](ONBOARDING.md) | +| Resolve a setup or delivery problem | [Troubleshooting](../setup/docs/Troubleshooting.md) | +| Find logs and set up alerts | [Monitoring](MONITORING.md) and [Application Insights](APPLICATION-INSIGHTS.md) | +| Choose identity and configuration alerts | [Three recommended Entra monitors](SIGN-IN-MONITORING.md#recommended-monitors), with sign-in investigation and alert setup | +| Understand the architecture, settings, or packages | [Technical reference](../TECHNICAL.md) and [HTTP contract](CONTRACT.md) | +| Explore multiple Azure regions | [Manual Front Door guide](FRONTDOOR.md); not included in guided setup | +| Build a custom implementation | [JavaScript](../javascript/README.md), [C#](../dotnet/README.md), or [Python](../python/README.md) | +| Implement the Entra ID-facing BYOT interface | [BYOT request and response guide](BRING-YOUR-OWN-PROVIDER.md); caller authentication, encrypted payload, and responses, with customer-owned service integration | +| Design primary-to-secondary provider fallback | [Developer walkthrough](MULTI-PROVIDER-IMPLEMENTATION.md); not a built-in feature | diff --git a/docs/MULTI-PROVIDER-IMPLEMENTATION.md b/docs/MULTI-PROVIDER-IMPLEMENTATION.md index c7f4a8b..0828d1a 100644 --- a/docs/MULTI-PROVIDER-IMPLEMENTATION.md +++ b/docs/MULTI-PROVIDER-IMPLEMENTATION.md @@ -10,7 +10,7 @@ The sample still ships with one provider per deployment and **no provider fallba describes customer code you must implement and review. A timeout, lost response or generic provider error is not proof of nonacceptance: in those cases, do not automatically send again. -For a normal deployment, follow the [main setup guide](../README.md) instead. This is an advanced +For a normal deployment, follow the [five-step setup guide](GETTING-STARTED.md) instead. This is an advanced developer walkthrough: it requires custom code, durable operation state, provider-specific approval, and separate testing. It is not another step in guided onboarding. diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index d63ebd6..bb6a5cb 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -1,6 +1,6 @@ # Customer configuration and validation -Start with the [five-step setup guide](../README.md). Come here when you need exact IDs, +Start with the [five-step setup guide](GETTING-STARTED.md). Come here when you need exact IDs, provider credential names, or the checks to complete before activation. **After successful guided setup, go straight to [provider authentication](#complete-provider-authentication), diff --git a/setup/docs/README.md b/setup/docs/README.md index 2e74f09..e8bb6ce 100644 --- a/setup/docs/README.md +++ b/setup/docs/README.md @@ -1,6 +1,6 @@ # EPP endpoint setup reference -New to the sample? Follow the [main setup guide](../../README.md). This reference explains the +New to the sample? Follow the [five-step setup guide](../../docs/GETTING-STARTED.md). This reference explains the permissions, hosting choices, script prompts, and maintenance steps in more detail. The three stages in this reference are: diff --git a/setup/docs/Troubleshooting.md b/setup/docs/Troubleshooting.md index 6be6c2b..1c27743 100644 --- a/setup/docs/Troubleshooting.md +++ b/setup/docs/Troubleshooting.md @@ -4,7 +4,7 @@ Find your symptom in the [triage table](#after-deployment-triage), then follow t If the setup script stopped, start with [safe recovery](#recover-without-destructive-cleanup) before rerunning it. A successful deployment does not yet prove provider access or delivery. -Return to the [main setup guide](../../README.md) when you are ready to continue onboarding. +Return to the [five-step setup guide](../../docs/GETTING-STARTED.md) when you are ready to continue onboarding. ## After-deployment triage From c2827d8f8db56b3248fd5796a18c50db78b0ef25 Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 12:19:29 -0700 Subject: [PATCH 02/13] docs: consolidate repeated setup and monitoring guidance Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 2 +- TECHNICAL.md | 105 ++++++++++++++--------------------- docs/APPLICATION-INSIGHTS.md | 80 ++++++++++---------------- docs/MONITORING.md | 83 +++++++++++---------------- 4 files changed, 103 insertions(+), 167 deletions(-) diff --git a/README.md b/README.md index 7474a7f..844c80c 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ See the [sample's production limitations](docs/CONTRACT.md#production-limitation - [Setup reference](setup/docs/README.md) and [troubleshooting](setup/docs/Troubleshooting.md) - [Provider configuration and validation](docs/ONBOARDING.md) -- [Monitoring](docs/MONITORING.md), [Entra sign-in alerts](docs/SIGN-IN-MONITORING.md#recommended-monitors), and [SMS cost estimation](docs/COST-CALCULATOR.md) +- [Monitoring](docs/MONITORING.md), [Microsoft Entra ID sign-in alerts](docs/SIGN-IN-MONITORING.md#recommended-monitors), and [SMS cost estimation](docs/COST-CALCULATOR.md) - [Technical reference](TECHNICAL.md), [HTTP contract](docs/CONTRACT.md), and [BYOT integration](docs/BRING-YOUR-OWN-PROVIDER.md) Optional multi-region and custom-provider designs are in the [detailed reference index](docs/GETTING-STARTED.md#find-more-detail); diff --git a/TECHNICAL.md b/TECHNICAL.md index 3485409..a6efd5e 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -39,44 +39,30 @@ neither of which is a successful evaluation. | C# (.NET isolated worker) | Available | [dotnet/](dotnet/) | | Python (v2 model) | Available | [python/](python/) | -All implementations conform to the **language-agnostic contract** in -[docs/CONTRACT.md](docs/CONTRACT.md): shared HTTP intent, config/env var names, Key Vault secret -names, and fail-closed/managed-identity/privacy boundaries. Internal provider APIs, binding errors, -credential caching, and telemetry differ by runtime; consult the documented differences rather -than assuming identical internals. +Choose **one language and one provider per Function App**; no provider is preferred or selected +by default. All implementations share the [HTTP, configuration, and trust contract](docs/CONTRACT.md), +but provider APIs, binding errors, credential caching, and telemetry can differ by runtime. -Choose one language and configure the adapter for your provider. No provider is preferred or selected -by default. Deploy each language separately, not all three to the same Function App. See the -[shared configuration](docs/CONTRACT.md#default-provider-and-configuration-readers). +For [local development or manual deployment](docs/ONBOARDING.md#optional-local-development-and-manual-deployment), +follow the selected runtime guide. For a custom service integration, start with the +[provider-neutral BYOT interface](docs/BRING-YOUR-OWN-PROVIDER.md). Your team owns the outbound +integration; that guide adds neither an adapter nor guided setup support. -For local development, manual configuration, and deployment checks, see the -[detailed configuration and validation guide](docs/ONBOARDING.md). +## Guided EPP setup -For the provider-neutral inbound interface, read the -[BYOT request and response guide](docs/BRING-YOUR-OWN-PROVIDER.md). It covers the Entra ID envelope, -caller authentication, encrypted delivery context, and endpoint responses. The outbound service -integration remains customer-owned; the guide does not add an adapter or guided setup support. +Follow the [five-step guide](docs/GETTING-STARTED.md), including its access and approval gates. +Download only `Setup-Epp.ps1`; it retrieves its support files from one commit and deploys after +you approve the complete plan. The [setup reference](setup/docs/README.md#step-2---download-and-run-one-script) +explains provider, route, runtime, hosting, and resource choices. -## Guided EPP setup +Review [application permissions and caller trust](setup/docs/README.md#step-1---manually-create-the-application), +including tenant-wide Graph `Application.Read.All`, and the +[certificate selection rules](setup/docs/README.md#encryption-certificate-selection). +Setup pins one PEM secret version and verifies Function access before changing registered encryption +credentials. Only the public certificate is downloaded. Replacement requires coordinated maintenance, +not zero-downtime rotation; certificate synchronization and policy activation remain manual. -Use **[setup](setup/docs/README.md)** for **Step 2: endpoint deployment**. Download only -`Setup-Epp.ps1`; it downloads its supporting PowerShell, Bicep, package catalog, and provider JSON -from the same commit. Customers select a language, provider, Global or EU endpoint, -a [service plan](setup/docs/README.md#service-plan-selection), and a resource prefix, then approve -one complete plan. Manual Step 1 only creates the dedicated app -registration; PowerShell configures its service principals, `Epp.Invoke`, Microsoft caller access, -Graph `Application.Read.All`, the provider-tenant allowlist preview, encryption certificate, and -Easy Auth. Read-only preflight discovers existing `AsymmetricX509Cert` / `Encrypt` credentials. -Customers choose a matching Key Vault certificate version to reuse, with explicit cleanup of any -other encryption credentials, or replacement with a new certificate. The approved deployment -stages the private-key reference, verifies its resolution by the Function system identity, and -registers exactly one public encryption certificate while preserving non-encryption credentials. -Setup downloads only the public certificate and pins the Function to its PEM secret version. -Reuse can reference another vault in the same tenant; provider credentials remain in the -setup-created vault. Replacement is coordinated maintenance, not zero-downtime rotation. -Each deployment accepts both SMS and voice. The request's `channel` selects its configured -provider endpoint; the two URLs can be identical or different. -Certificate synchronization and policy activation remain manual. The home tenant remains allowed by Entra. +One deployment accepts SMS and voice; each request's `channel` selects its configured provider URL. ## Download a Function ZIP @@ -93,21 +79,17 @@ Download the latest successful CI ZIP for your chosen language: | .NET | [epp-dotnet-source.zip](https://github.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/releases/latest/download/epp-dotnet-source.zip) | C# Function source and project file; build/publish before deployment | | Python | [epp-python-source.zip](https://github.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/releases/latest/download/epp-python-source.zip) | Source for Azure remote build on Linux | -Customers do not need PowerShell or a local build toolchain to download these files. Verify downloads -against the corresponding release's `SHA256SUMS.txt`. Configure the target Function App's runtime, app settings, -Key Vault access, and Easy Auth before deploying. .NET requires building/publishing the extracted -project; Python requires remote build to install dependencies. Neither source ZIP can run directly -as a run-from-package artifact. GitHub's **Code > Download ZIP** -is the whole source repository, not a Function deployment package. - -Each successful `main` build tests all three implementations, builds and inspects the ZIPs, and -publishes a new versioned release marked as the latest release. -The direct links above and guided setup therefore track the newest successful CI package build. Get builds from -[Latest release](https://github.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/releases/latest). -Older releases remain available; existing assets are not overwritten. Pull requests build downloadable -workflow artifacts only and cannot publish releases. GitHub sign-in may be required for workflow -artifacts, but public release downloads do not require a local build. Packaging does not deploy or -verify live provider delivery. +Downloads need no PowerShell or build tools. Verify the release's `SHA256SUMS.txt`, then configure +the target runtime, app settings, Key Vault access, and **Easy Auth before deployment**. +Build/publish .NET source; use Azure remote build for Python dependencies. Neither source ZIP is +directly runnable as a run-from-package artifact. **Code > Download ZIP** is the repository, not +a deployment package. + +Each successful `main` build tests all three runtimes and inspects the ZIPs before publishing a +versioned [latest release](https://github.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/releases/latest). +These links and guided setup track that release; older assets remain available and are not overwritten. +PR builds publish workflow artifacts only, which may require GitHub sign-in. Public releases do not. +Packaging neither deploys nor verifies provider delivery. ## Build ZIPs Locally @@ -191,10 +173,9 @@ is running**; do not copy `UseDevelopmentStorage=true` into Azure. Optional sett below rather than appearing as required placeholders in the sample. Keep explanatory comments outside `Values`, otherwise the host loads them as environment variables too. -The local settings file is an environment-variable input for the Functions host, **not a serialized -`AppConfig` or request model**. For example, `EPP_PROVIDER_NAME` becomes `config.providerName` in -JavaScript, `config.provider_name` in Python, and `config.ProviderName` in .NET. The refactor changed -how code accesses configuration; request fields remain separate from deployment settings. +Local settings supply environment variables, **not a serialized `AppConfig` or request model**. +For example, `EPP_PROVIDER_NAME` becomes `config.providerName` in JavaScript, +`config.provider_name` in Python, and `config.ProviderName` in .NET. | Variable | When needed | Value | |---|---|---| @@ -237,18 +218,14 @@ login; ordinary local machines have no managed-identity endpoint. Use offline te evaluation locally, or an explicitly injected test resolver for integration work. Never commit local settings, keys or test credentials. -When their selected cache is enabled, configured providers are -[prepared per worker](docs/CONTRACT.md#credential-caching-and-refresh). -JavaScript/Python warm credentials and poll for refresh; .NET warms at startup and retrieves -replacements on cache misses, without a periodic poller. Credential acquisition never sends an OTP. -Evaluation skips provider work, but configured workers can independently acquire credentials at -startup. Disabling the selected cache skips startup preparation, polling, and cross-request reuse, -but live requests still acquire credentials. Each switch accepts trimmed, case-insensitive `true` or -`false`; an invalid selected value fails live credential resolution closed without preventing evaluation. -The other provider-auth mode's switch is ignored. Setup writes both as `false` for FC1 or `true` for -EP1; older packages that do not read these settings still require a supporting release. Restart after -changes. Leave `EPP_PROVIDER_NAME` unset or disable its cache for local evaluation-only work without -credential acquisition. Platform-managed identity caching and decryption-key references are separate. +For cache values, failure behavior, and runtime differences, use the +[credential caching contract](docs/CONTRACT.md#credential-caching-and-refresh). +JavaScript/Python poll; .NET refreshes on cache misses. Setup writes both switches as `false` for FC1 +or `true` for EP1; deploy a supporting package and restart after changes. +Evaluation skips provider work, but cached workers can independently acquire credentials without +sending an OTP. Leave `EPP_PROVIDER_NAME` unset or disable its cache for local evaluation without +credential acquisition. Provider caching is separate from platform-managed identity caching and +decryption-key references. Core Tools does not resolve Azure Key Vault reference expressions locally. Supply the local test PEM or base64 PEM directly; use a reference such as `@Microsoft.KeyVault(SecretUri=https://.vault.azure.net/secrets//)` diff --git a/docs/APPLICATION-INSIGHTS.md b/docs/APPLICATION-INSIGHTS.md index 514fb77..7de470a 100644 --- a/docs/APPLICATION-INSIGHTS.md +++ b/docs/APPLICATION-INSIGHTS.md @@ -1,41 +1,26 @@ # Application Insights for the External Phone Provider -Application Insights helps you see which requests ran, how long they took, and what the Function -reported along the way. It does **not** confirm that an SMS or voice call reached the recipient. +**Find logs and create alerts:** use the [monitoring walkthrough](MONITORING.md). +**Check collection or missing data:** start with [single-region configuration](#single-region-configuration) +below. This reference covers JavaScript, .NET isolated, and Python; Front Door is not required. -**Looking for your logs?** Start with the [monitoring guide](MONITORING.md). It shows how to find -the resources from your deployment summary, run queries, and create alerts. +Function telemetry describes execution, timing, and logs, **not recipient delivery**. +[Microsoft Entra ID sign-in monitoring](SIGN-IN-MONITORING.md) uses a separate tenant export pipeline and does +not prove delivery either. -For user authentication outcomes, see [Microsoft Entra sign-in monitoring](SIGN-IN-MONITORING.md). -It uses a separate tenant log-export pipeline, not Function telemetry; neither source alone -proves phone delivery. - -This reference explains how collection works for **JavaScript, .NET isolated, and Python**, -what each signal means, and why data may be missing. Start with -[single-region configuration](#single-region-configuration) to check a guided deployment; -use the remaining sections for runtime differences or a multi-region design. -You do not need Azure Front Door for Function telemetry. - -These instructions do not deploy resources or enable extra instrumentation, alerts, or availability -tests. Use the [setup guide](../setup/docs/README.md) for deployment and the -[approved test handoff](ONBOARDING.md#validate-the-deployed-endpoint) to generate test traffic. +These instructions do not deploy resources or enable instrumentation, alerts, or availability tests. +Use the [setup guide](GETTING-STARTED.md) and [approved test handoff](ONBOARDING.md#validate-the-deployed-endpoint). ## How telemetry reaches Application Insights -There are two execution processes to consider: the **Functions host**, which dispatches -invocations, and the **language worker**, which runs the endpoint code. - -1. The host records invocation telemetry. With the standard Functions logging pipeline, - application logs are forwarded from the worker to the host for export. -2. A worker can instead have its own SDK or OpenTelemetry instrumentation and exporter. - That pipeline requires language-specific initialization, authentication, filtering, and - sampling; configuring the host alone does not configure every worker signal. -3. The exporter sends telemetry to the Application Insights ingestion endpoint. The - Application Insights resource identifies the application and its linked Log Analytics - workspace stores the log records. -4. Application Insights views and Azure Monitor Logs queries use those records. Correlation - identifiers can connect requests, dependencies, and logs when instrumentation propagates - the context. Background work does not necessarily belong to a request. +The **Functions host** dispatches invocations and records their telemetry; the **language worker** +runs endpoint code. Standard worker logs pass through the host. A direct worker SDK/OpenTelemetry +exporter instead needs its own initialization, authentication, filtering, and sampling. +Host configuration alone does not configure every worker signal. + +Exporters send data to Application Insights; its linked Log Analytics workspace stores records +for views and queries. Propagated correlation context connects requests, dependencies, and logs. +Background work need not belong to a request. In OpenTelemetry terminology, a distributed trace contains spans. In Application Insights, server spans generally appear as **requests**, client spans as **dependencies**, and @@ -60,13 +45,12 @@ These are source defaults, not an inventory of a running deployment. Deployed pa application-setting overrides, runtime versions, and log filters can change the result. The guided setup can publish a released package rather than the current source. -The endpoint emits application events such as `request_received`, `provider_response_processed`, -`request_failed`, `request_completed`, and `credential_refresh_failed` through language logging -APIs. These are **log events**, not a promise of rows in `customEvents`. .NET uses structured -`ILogger` events, JavaScript uses its logging helper, and Python uses logging records with extra -fields. Message formatting, property names/casing, and preservation of extra fields depend on -the logging/export path. Inspect the actual schema before sharing one event-field query across -languages. +Events such as `request_received`, `provider_response_processed`, `request_failed`, +`request_completed`, and `credential_refresh_failed` are **logs**, not guaranteed `customEvents`. +Use [runtime-specific log discovery](MONITORING.md#runtime-specific-log-discovery) to check +JavaScript JSON, Python logging extras, or .NET `ILogger` fields in the deployed schema. +Exporters can change formatting, casing, and which properties survive; queries are not portable +without that check. When adding direct worker instrumentation in a future code change, follow the [Functions OpenTelemetry guidance](https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto) @@ -184,18 +168,14 @@ proof of the Function's execution region. `AppRoleInstance` identifies an instan regional label. In a shared component, use verified role naming or an explicitly emitted region property; do not expect Azure resource tags to become telemetry properties automatically. -For separate workspaces, use -[cross-workspace queries](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/cross-workspace-query) -with explicit `workspace("").AppRequests` references and a region label -for each source before the union. The placeholders must be replaced by authorized operators, -and the query identity needs access to all sources. Group by origin before aggregating so a -healthy region cannot hide a failing one. Check each region's latest telemetry, including idle -or standby regions; no traffic and broken collection can both look like an empty result. - -If Front Door is used, inspect its platform metrics and diagnostic logs separately. An edge or -WAF rejection, origin-connectivity failure, or health-probe result is not necessarily a Function -request. Front Door origin health is not evidence of provider authentication or message delivery. -The [optional Front Door guide](FRONTDOOR.md) describes that topology. +Use the [cross-workspace queries and missing-origin check](MONITORING.md#3-multiple-regions-and-cross-workspace-queries) +with explicit sources, authorized access to each, and per-origin grouping. Check idle/standby +origins too: no traffic and broken collection can both look empty. + +For [optional Front Door](FRONTDOOR.md), inspect +[edge metrics and logs separately](MONITORING.md#8-optional-azure-front-door-layer). +Edge/WAF rejections, origin-connectivity failures, and probes need not appear as Function requests. +Origin health does not prove provider authentication or delivery. ## Read-only queries to establish what is present diff --git a/docs/MONITORING.md b/docs/MONITORING.md index 54f0f58..c4d08b5 100644 --- a/docs/MONITORING.md +++ b/docs/MONITORING.md @@ -3,10 +3,9 @@ Use this guide to find your endpoint's logs and set up alerts. Guided setup creates Application Insights and a Log Analytics workspace, but **you still need to configure and test notifications**. -For **tenant authentication outcomes and potential abuse signals**, use the separate -[Microsoft Entra sign-in monitoring guide](SIGN-IN-MONITORING.md). Entra log export and -sign-in alerts are not configured by endpoint setup; sign-in failures are not automatically -provider failures or evidence of OTP delivery. +For **tenant authentication and potential abuse signals**, use +[Microsoft Entra ID sign-in monitoring](SIGN-IN-MONITORING.md). Endpoint setup does not configure that pipeline; +sign-in failures do not by themselves identify provider failures or prove OTP delivery. For your first single-region deployment: @@ -41,12 +40,10 @@ Keep a private inventory with one row per expected regional endpoint: Do not derive the expected inventory solely from recent telemetry: an origin that never reports would be absent from that list. -The [guided setup](../setup/docs/README.md) configures workspace-based Application -Insights, an ingestion identity/role, and Function diagnostic settings. Inspect the -actual deployment rather than assuming that configuration succeeded. Other deployment -methods can differ. The current setup template requests 30-day retention; confirm -effective workspace/table retention and organizational policy before changing it. -It does not create the action groups, Workbooks, or alert rules described here. +[Guided setup](../setup/docs/README.md) creates the collection resources, not action groups, +Workbooks, or alerts. [Verify the deployed collection configuration](APPLICATION-INSIGHTS.md#single-region-configuration); +resource creation alone does not prove ingestion, and other deployment methods can differ. +The template requests 30-day retention; check effective workspace/table retention and policy before changes. ### Find the linked resources in the portal @@ -65,13 +62,10 @@ These are real portal views of an existing test deployment, with identifying inf connection details permanently hidden. Resource names, regions, and portal layouts may differ. The links show where to navigate; they do not prove that test traffic or delivery succeeded. -In the Azure portal, inspect each Function's Application Insights association, -monitoring settings, managed-identity ingestion authorization, and diagnostic -destinations. Do not expose connection strings or credential values. Application -Insights telemetry and resource diagnostic logs are different pipelines: -`AppRequests`/`AppTraces` are not interchangeable with `FunctionAppLogs` or -`AzureDiagnostics`. Enabling diagnostic logs alone does not prove request telemetry -is arriving, and platform metrics do not require log export to appear in Metrics. +Keep connection strings and credentials private. +`AppRequests`/`AppTraces` are not interchangeable with `FunctionAppLogs` or `AzureDiagnostics`. +Diagnostic export alone does not prove request collection; platform metrics need no log export. +See [pipelines and table names](APPLICATION-INSIGHTS.md#signals-tables-and-metrics-are-different-things). Use **Log Analytics workspace > Logs** for all KQL below. Application Insights' application-context query experience can instead use names such as `requests`, @@ -270,24 +264,14 @@ required; the request query is not a synthetic scheduler. ## 4. JavaScript service-event queries -The JavaScript implementation emits JSON messages with `logType: "service"` and -`eventName` through its invocation logger. The following examples assume those -messages arrive as JSON in `AppTraces.Message`. They are **not portable event -parsers for every runtime**: - -- JavaScript and Python `host.json` enable Application Insights sampling and exclude - `Request` telemetry from that host sampling. Trace events can still be sampled - or filtered. The setting is not a guarantee against ingestion loss. -- Python uses logging messages plus `extra` fields such as `event_name`, not this - JavaScript JSON contract. Whether those fields reach custom properties depends - on the deployed logging pipeline. -- .NET isolated uses `ILogger` events and declares OpenTelemetry in `host.json`. - Inspect the actual host/worker exporter configuration, tables, and properties; - do not assume JavaScript JSON or complete worker telemetry. - -Verify a controlled request appears in `AppRequests` and that its expected events -appear in the deployed trace schema before relying on any service-event alert. -An empty JSON-filtered result can mean a parser/pipeline mismatch. +These queries require JavaScript invocation logs containing `logType: "service"` and `eventName` +as JSON in `AppTraces.Message`. **Do not apply this parser to Python or .NET**; use +[runtime-specific discovery](#runtime-specific-log-discovery). + +Verify a controlled request and its events before creating alerts. Traces can be sampled or +filtered, and an empty JSON result can mean a parser/pipeline mismatch. Check +[runtime collection defaults](APPLICATION-INSIGHTS.md#what-the-checked-in-sample-configures) +and [sampling limits](APPLICATION-INSIGHTS.md#sampling-privacy-and-collection-limits). ### Separate evaluated, accepted, and failed completions @@ -407,11 +391,10 @@ AppTraces | summarize StoredRows = count() by SchemaStatus, Result, HttpStatus ``` -These discovery counts are **stored rows**, not sampled traffic totals or delivery receipts. -Do not turn them into alerts until the selected release's schema and collection are verified. -In particular, the .NET host's OpenTelemetry declaration is not an initialized direct worker -exporter. Missing worker events require a collection investigation, not a green application -health result. Malformed .NET request binding can fail before the handler emits any events. +These are **stored rows**, not traffic totals or delivery receipts. Verify the release's schema and +collection before creating alerts. Missing events require investigation, not a healthy status: +.NET host OpenTelemetry does not initialize a direct worker exporter, and malformed .NET binding +can fail before handler events. See [collection configuration](APPLICATION-INSIGHTS.md#what-the-checked-in-sample-configures). For an incident, use the host `OperationId`/invocation ID and whichever application request ID is actually exported. Keep raw Microsoft/provider support IDs distinct; not every runtime @@ -470,18 +453,14 @@ not validate those dependencies' future readiness. ### Certificate, credential, and cost ownership -Keep the setup summary's `certificateExpiresUtc`, vault certificate/secret versions, and Entra -encryption credential expiry in a restricted inventory with a renewal owner and escalation backup. -For certificates issued by setup, configure vault-wide certificate contacts and retain the -`EmailContacts` lifetime action. For a reused certificate, coordinate its existing issuance policy -and notifications with the source-vault owner. Follow the -[renewal runbook](../setup/docs/README.md#encryption-certificate-lifecycle). -Contacts alone are not automatic renewal, and a new vault certificate version does not update -the Function's version-pinned reference or Entra by itself. - -Create an independent reminder before the certificate's 30-day near-expiry window and rehearse -the approved single-certificate renewal/replacement procedure in nonproduction. If your operations -process uses Key Vault near-expiry/new-version events, an operator must configure an +Keep `certificateExpiresUtc`, vault certificate/secret versions, and Entra encryption credential +expiry in a restricted inventory with a renewal owner and backup. Follow the +[renewal runbook](../setup/docs/README.md#encryption-certificate-lifecycle) for vault-wide contacts, +`EmailContacts`, and reused-certificate policies. **Contacts do not renew certificates; a new vault +version does not update the Function's pinned reference or Entra.** + +Set an independent reminder before the 30-day near-expiry window and rehearse approved renewal +in nonproduction. For Key Vault near-expiry/new-version events, configure an [Event Grid subscription and handler](https://learn.microsoft.com/azure/key-vault/general/event-grid-overview) and test its notification path. Diagnostic settings alone do not create these alerts, and those events do not automatically become Application Insights traces. From 03a0002a56d9f6243d03f804e426bcaf8dff75d4 Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 13:22:54 -0700 Subject: [PATCH 03/13] docs: organize guides around tasks and safe troubleshooting Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 4 +- TECHNICAL.md | 51 ++--- docs/APPLICATION-INSIGHTS.md | 26 ++- docs/BRING-YOUR-OWN-PROVIDER.md | 6 + docs/CONTRACT.md | 153 ++------------ docs/FRONTDOOR.md | 75 +++---- docs/GETTING-STARTED.md | 106 ++++------ docs/LOGGING.md | 129 ++++++++++++ docs/MONITORING.md | 82 +++----- docs/MULTI-PROVIDER-IMPLEMENTATION.md | 41 ++-- docs/ONBOARDING.md | 113 +++++----- docs/README.md | 44 ++++ docs/SIGN-IN-MONITORING.md | 99 +++++---- dotnet/README.md | 120 +++++------ javascript/README.md | 119 ++++------- python/README.md | 107 ++++------ setup/docs/README.md | 241 +++++++++------------ setup/docs/Troubleshooting.md | 292 ++++++++++++-------------- 18 files changed, 815 insertions(+), 993 deletions(-) create mode 100644 docs/LOGGING.md create mode 100644 docs/README.md diff --git a/README.md b/README.md index 844c80c..7accc90 100644 --- a/README.md +++ b/README.md @@ -39,5 +39,5 @@ See the [sample's production limitations](docs/CONTRACT.md#production-limitation - [Monitoring](docs/MONITORING.md), [Microsoft Entra ID sign-in alerts](docs/SIGN-IN-MONITORING.md#recommended-monitors), and [SMS cost estimation](docs/COST-CALCULATOR.md) - [Technical reference](TECHNICAL.md), [HTTP contract](docs/CONTRACT.md), and [BYOT integration](docs/BRING-YOUR-OWN-PROVIDER.md) -Optional multi-region and custom-provider designs are in the [detailed reference index](docs/GETTING-STARTED.md#find-more-detail); -they are not built-in guided setup features. +Use the [documentation index](docs/README.md) for deployment, troubleshooting, operations, and development. +Optional multi-region and provider-fallback designs are not built-in guided setup features. diff --git a/TECHNICAL.md b/TECHNICAL.md index a6efd5e..39a7bbf 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -1,16 +1,17 @@ # External Phone Provider: Technical Reference -For the single-region customer deployment flow, start with the -[five-step setup guide](docs/GETTING-STARTED.md). +[Documentation](docs/README.md) | [First deployment](docs/GETTING-STARTED.md) | [HTTP contract](docs/CONTRACT.md) + +Use this page for implementation choices, settings, and packages. +Jump to [implementations](#implementations), [guided setup](#guided-epp-setup), +[download](#download-a-function-zip), [build](#build-zips-locally), +[settings](#configure-environment-variables), [provider details](#provider-specific-integration-details), +or [security](#security). Azure Front Door is an [optional manual multi-region design](docs/FRONTDOOR.md), not a feature enabled by the single-region setup script. Its additional readiness endpoint is not shipped in the release packages, and regional failover does not change the SendOtp contract. -Use this reference when developing or customizing the endpoint. Each language folder implements -the same [HTTP contract](docs/CONTRACT.md), with provider-specific adapters, environment settings, -and managed-identity access to credentials. - ## Single-region architecture See the [single-region diagram in the sample overview](README.md#architecture). @@ -151,8 +152,6 @@ Only provider acceptance returns the nonce for live requests. Incoming `mode: 2` non-delivery path: after platform authentication, validate and decrypt, then echo the nonce without calling a provider. -See [docs/CONTRACT.md](docs/CONTRACT.md) for the full specification every implementation follows. - Request `tenantId`, `channel`, `mode` and `ttlSeconds` are request data, not extra environment settings. The trusted tenant issuer, endpoint-app audience and authorized Microsoft caller are configured in Easy Auth, not in application environment settings or incoming request data. @@ -287,7 +286,7 @@ omits that header on live sends and does not forward it from incoming requests. lookup entirely, rather than invoking Telesign shutter mode. Responses normalize `reference_id` and `status.code`/`status.description` internally; provider -metadata is not exposed in the public nonce response. [Application logs](docs/CONTRACT.md#application-logs) +metadata is not exposed in the public nonce response. [Application logs](docs/LOGGING.md#safe-fields-and-identifiers) include only the provider HTTP status, mapped status/outcome and the bounded raw provider reference ID as `providerMessageId` for support lookup, never the raw response or description. Existing numeric success codes are retained (SMS: 200, 203, 290-292; Voice: 100-103). EPP code `3001` ("Message in progress"), @@ -299,34 +298,20 @@ account access with Telesign before production. ## Security -**Easy Auth (App Service Authentication) is the only caller-authentication gate, before the anonymous -Function.** Enable it with `requireAuthentication=true`, `unauthenticatedClientAction=Return401` and -`requireHttps=true`. Configure the trusted tenant issuer and `allowedAudiences` for the endpoint app, -and a **nonempty `allowedApplications`** list pinned to the authorized Microsoft caller application ID. -Do not exclude the SendOtp path. The handler does not parse or validate bearer tokens, and there is -no backup application validation or function-key gate. **Never expose this endpoint to the public -internet with Easy Auth disabled or bypassed.** See [platform setup](docs/ONBOARDING.md#2-provision-encryption-and-deployment-trust). - -JWE decryption protects the payload but **does not authenticate the caller**: anyone with the public key can -encrypt a request. A nonce echo, including a fixed nonce, is not caller authentication. Provider API -keys are read from **Key Vault** via **managed identity**; they authenticate the outbound provider call, -not the inbound request. +**Easy Auth is the only caller-authentication gate before the anonymous Function.** +Follow the [exact trust checklist](docs/ONBOARDING.md#manual-deployment-trust-checklist) for HTTPS, +issuer, audience, caller allowlist, and unauthenticated rejection. Never disable it or exempt SendOtp; +there is no handler token validator or function-key fallback. -Core Tools does not provide Easy Auth. Local execution is unauthenticated: bind only to loopback, -with no tunnels or public forwarding. Offline tests cover application behavior, not platform -authentication; [separate deployed security checks](docs/ONBOARDING.md#4-package-deploy-and-verify) are required. +JWE and its nonce prove no caller identity: anyone with the public key can encrypt. +Provider credentials authenticate outbound calls, not incoming requests; vault access uses managed identity. +Core Tools has no Easy Auth: bind locally to loopback without tunnels. +[Deployed checks](docs/ONBOARDING.md#validate-the-deployed-endpoint) remain required after offline tests. ## Docs -- **[README.md](README.md)**: sample overview and architecture. -- **[Setup guide](docs/GETTING-STARTED.md)**: single-region customer onboarding. -- **[docs/ONBOARDING.md](docs/ONBOARDING.md)**: customer value sources, provider handoffs, validation, and optional manual deployment. -- **[docs/FRONTDOOR.md](docs/FRONTDOOR.md)**: optional manual multi-region onboarding and observed failover limitations. -- **[docs/CONTRACT.md](docs/CONTRACT.md)**: the language-agnostic contract every implementation follows. -- **[Application logs](docs/CONTRACT.md#application-logs)**: separate service and completion events, - and the meaning of Microsoft, Function and provider identifier fields. -- **[Application Insights](docs/APPLICATION-INSIGHTS.md)** and **[monitoring](docs/MONITORING.md)**: - collection, runtime-aware queries, alert setup, and ongoing operations. +Use the [documentation index](docs/README.md) for setup, troubleshooting, operations, and advanced designs. +For code work, keep the [HTTP contract](docs/CONTRACT.md) and [log schema](docs/LOGGING.md) together. ## Contributing a language or provider diff --git a/docs/APPLICATION-INSIGHTS.md b/docs/APPLICATION-INSIGHTS.md index 7de470a..d1f1c56 100644 --- a/docs/APPLICATION-INSIGHTS.md +++ b/docs/APPLICATION-INSIGHTS.md @@ -1,5 +1,7 @@ # Application Insights for the External Phone Provider +[Documentation](README.md) | [Monitoring walkthrough](MONITORING.md) | [Log schema](LOGGING.md) + **Find logs and create alerts:** use the [monitoring walkthrough](MONITORING.md). **Check collection or missing data:** start with [single-region configuration](#single-region-configuration) below. This reference covers JavaScript, .NET isolated, and Python; Front Door is not required. @@ -11,6 +13,12 @@ not prove delivery either. These instructions do not deploy resources or enable instrumentation, alerts, or availability tests. Use the [setup guide](GETTING-STARTED.md) and [approved test handoff](ONBOARDING.md#validate-the-deployed-endpoint). +Jump to [collection pipelines](#how-telemetry-reaches-application-insights), +[signals and tables](#signals-tables-and-metrics-are-different-things), +[single-region checks](#single-region-configuration), [ingestion identity](#identity-and-ingestion), +[multi-region layout](#multi-region-configuration), [coverage queries](#read-only-queries-to-establish-what-is-present), +or [sampling/privacy limits](#sampling-privacy-and-collection-limits). + ## How telemetry reaches Application Insights The **Functions host** dispatches invocations and records their telemetry; the **language worker** @@ -45,19 +53,15 @@ These are source defaults, not an inventory of a running deployment. Deployed pa application-setting overrides, runtime versions, and log filters can change the result. The guided setup can publish a released package rather than the current source. -Events such as `request_received`, `provider_response_processed`, `request_failed`, -`request_completed`, and `credential_refresh_failed` are **logs**, not guaranteed `customEvents`. -Use [runtime-specific log discovery](MONITORING.md#runtime-specific-log-discovery) to check -JavaScript JSON, Python logging extras, or .NET `ILogger` fields in the deployed schema. -Exporters can change formatting, casing, and which properties survive; queries are not portable -without that check. +[Application events](LOGGING.md#event-sequence) are **logs**, not guaranteed `customEvents`. +Use [runtime-specific discovery](MONITORING.md#runtime-specific-log-discovery) before sharing queries: +exporters can change formatting, casing, and which properties survive. -When adding direct worker instrumentation in a future code change, follow the +To add direct worker instrumentation, follow the [Functions OpenTelemetry guidance](https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto) -for the selected language. Initialize the exporter and required instrumentations, configure -its credential, and verify correlation and duplicate suppression. Do not simply copy another -language's host settings. Other Azure Functions languages are outside this repository's sample; -use the corresponding language tab in the Microsoft guidance for their support requirements. +for your language: initialize exporter/instrumentations and credentials, then verify correlation +and duplicate suppression. Do not copy another runtime's host settings. For languages outside +this sample, check Microsoft's corresponding support tab. ## Signals, tables, and metrics are different things diff --git a/docs/BRING-YOUR-OWN-PROVIDER.md b/docs/BRING-YOUR-OWN-PROVIDER.md index 7f6fb69..7a56472 100644 --- a/docs/BRING-YOUR-OWN-PROVIDER.md +++ b/docs/BRING-YOUR-OWN-PROVIDER.md @@ -1,5 +1,7 @@ # BYOT integration: the Entra ID request and response +[Documentation](README.md) | [Exact HTTP contract](CONTRACT.md) | [Runtime guides](README.md#develop) + **Microsoft Entra ID** sends your **External Phone Provider (EPP)** endpoint an encrypted phone number and one-time passcode message. Your endpoint verifies the caller, decrypts the details, and either evaluates the request without sending or uses your phone service. @@ -9,6 +11,10 @@ Before deploying, ask your tenant administrator to confirm [eligibility, testing, and activation with Microsoft](ONBOARDING.md#before-purchasing-or-deploying). Deploying the endpoint alone does not enable tenant policy. +Follow [caller verification](#1-verify-the-caller), [decryption](#2-read-and-decrypt-the-request), +[evaluation/live handling](#3-evaluate-or-send), and [responses](#4-respond-to-entra-id). +Then [register your integration](#where-to-add-your-integration) and [validate before activation](#before-activation). + ## 1. Verify the caller diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md index d9b5f36..673d570 100644 --- a/docs/CONTRACT.md +++ b/docs/CONTRACT.md @@ -1,8 +1,15 @@ # External Phone Provider Function: Language-Agnostic Contract +[Documentation](README.md) | [Implementation walkthrough](BRING-YOUR-OWN-PROVIDER.md) | [Settings and packages](../TECHNICAL.md) + This defines the shared contract for [JavaScript](../javascript/), [Python](../python/) and [.NET](../dotnet/). See [production limitations](#production-limitations) before production use. +Jump to [request/response](#1-http-api), [HTTP outcomes](#2-outcome--http-status-mapping), +[provider contract](#3-provider-contract), [settings](#4-configuration-app-settings--env), +[caching](#credential-caching-and-refresh), [required behaviors](#5-required-behaviors), +[logs](#application-logs), or [tests](#6-lightweight-tests). + > **Naming.** EPP means **External Phone Provider**. App settings use the `EPP_` prefix; the > request and delivery models are `Envelope`, `DeliveryContext` and provider-neutral OTP delivery. > Documentation names do not change the external JSON fields or `microsoft.mfa.otpDeliver.v1` version. @@ -286,7 +293,7 @@ the transport result and returns a normalized provider result. Raw API-specific by the Function and never enters public responses or logs. This model is internal: do not serialize it into the endpoint response or logs. The -[request logger](#application-logs) selects only the provider HTTP status, a recognized normalized +[request logger](LOGGING.md) selects only the provider HTTP status, a recognized normalized status, the mapped outcome and a bounded raw provider message ID; descriptions and raw metadata remain private. Public HTTP responses still expose only the existing nonce/correlation/status or sanitized error contract. @@ -478,143 +485,13 @@ Do not log SDK exception details or tokens. No cache retries/deduplicates provid ### Application logs -JavaScript emits JSON service records with an immutable request context. Python uses the standard -`logging` pipeline: `otp_log.py` defines fixed event IDs, names, levels and fields, while an immutable -`LoggerAdapter` context supplies the Function and Microsoft trace identifiers. The configured -logging provider owns Python output formatting and export. Neither runtime manually builds a mutable -comprehensive request summary; each invocation ends with a fixed `request_completed` event. - -Likewise, .NET uses the standard `ILogger` pipeline instead of manually serializing JSON. `OtpLog` -defines source-generated events with stable IDs and names, while `ILogger.BeginScope` supplies -`FunctionName`, `FunctionRequestId`, `FunctionInvocationId`, `MsClientRequestId`, -`MsCorrelationId` and `MsCorrelationIdSource`. The configured logging provider owns output -formatting and export. .NET emits `request_completed` as an ordinary typed event rather than a -mutable comprehensive summary. JavaScript follows the same fixed-event model while retaining its -existing JSON field names. - -A successful live request in each runtime emits: - -`request_received`, `payload_validated` (`envelope_validated` in JavaScript), -`delivery_context_decrypted`, `provider_selected`, -`provider_credential_resolution_started`, `provider_credential_resolved`, -`provider_request_build_started`, `provider_request_built`, `provider_request_started`, -`provider_response_received`, `provider_response_processed`, `response_prepared`, -`request_completed`. - -Failures emit `request_failed` with fixed `FailureStage`, `FailureReason` and `HttpStatus` values. -Provider results retain the coarse endpoint `Outcome` plus a safe diagnostic classification: -`provider_http_error`, `provider_rejected`, `unrecognized_provider_status`, -`missing_provider_message_id` or `invalid_provider_json`. The structured -`provider_response_processed` event includes this classification together with the upstream HTTP -status and normalized provider status; raw provider descriptions and bodies remain excluded. -Credential refresh failures use `credential_refresh_failed`; unexpected unclassified failures use -`unexpected_error`. Typed event arguments are explicitly allowlisted and never include request -bodies, decrypted delivery fields, credentials, provider response bodies, query strings or private -exception messages. Endpoint values contain only scheme, host/port and path. Evaluation omits all -provider events and emits `evaluation_completed`. - -A successful live request emits these separate service events: - -| Service event | Safe information recorded | -|---|---| -| `request_received` | Function invocation and available raw Microsoft trace IDs under their `x-ms-*` names; no raw body or arbitrary headers. | -| `envelope_validated` / `payload_validated` | Allowlisted body metadata: validated payload type, normalized `channel`, `evaluation` and optional `ttlSeconds`. | -| `delivery_context_decrypted` | Decryption completed; no plaintext fields, JWE or key ID. | -| `provider_selected` | Registered provider and its authentication mode. | -| `provider_credential_resolution_started` | OAuth client-assertion or Key Vault credential source, with explicitly named raw OAuth application/identity/tenant IDs. | -| `provider_credential_resolved` | Credential resolution and usability checks completed, with elapsed time; no secret, token, assertion or claims. | -| `provider_request_build_started` | The provider is preparing the outbound request. | -| `provider_request_built` | Allowlisted HTTP method, final endpoint scheme/host/port/API path, HTTPS and disabled redirects; no query string, authorization headers or body. | -| `provider_request_started` | The outbound send is beginning, with method, sanitized endpoint and timeout. | -| `provider_response_received` | Actual upstream HTTP status; emitted before response-body reading completes. | -| `provider_response_processed` | Mapped provider status/outcome, fixed failure classification and duration. Raw provider descriptions and bodies are excluded. | -| `response_prepared` | Response status and booleans indicating nonce/correlation inclusion, not their values or the response body. | - -Body metadata is built from validated fields, **not** from a body dump with a few sensitive -properties removed. Unknown request properties, `tenantId`, locale/risk data and all decrypted -fields remain excluded. An invalid envelope never contributes unvalidated type or TTL values. - -`providerCredentialSource` is `managed_identity_client_assertion` for the app-token path and -`key_vault` for API-key providers. Resolution may use cached credentials: these events do not claim -a Key Vault network fetch or a fresh identity/token exchange occurred. `providerCredentialElapsedMs` -covers resolution plus the existing credential checks, not a separately enforced deadline. SDK -diagnostics remain suppressed as before; logging adds no token acquisition, secret reads or retries. - -`provider_request_built` describes the provider request, not creation of a new physical HTTP connection -or a new HTTP client in every runtime. `providerEndpoint` uses the **final** provider URL, which can -differ from the configured base URL, but records only scheme + host/port + API path. For example, -`https://provider.example/epp/voice?key=secret` is logged as `https://provider.example/epp/voice`. -Userinfo, the entire query string and fragments are excluded. Use provider-approved paths that do -not embed secrets or personal data; path segments are not redacted. This does not alter the outbound -URL or its required query parameters. Standard HTTP verbs are logged in uppercase; custom or invalid -values are represented as `other` without changing the request sent. - -`response_prepared` is emitted on success **and failure** immediately before returning the handler -response. It does not claim the host has serialized/transmitted that response or Microsoft received -it; consult platform request telemetry for transport completion. Evaluation emits -`evaluation_completed` instead of provider events, then `response_prepared` and `request_completed`, -without resolving provider configuration, credentials or HTTP. Failures emit the fixed -`request_failed` event with a safe stage, reason and HTTP status. -A parsed provider rejection uses `provider_response_processed` with its non-success outcome and -fixed failure reason. - -In JavaScript and Python, every service event carries the Function request/invocation IDs, the Microsoft trace IDs available -at that point. JavaScript also carries known channel/evaluation/provider context; Python supplies -selected context through its logging adapter and event arguments. The initial event can -only know header trace IDs; a valid envelope can subsequently supply the selected correlation. -The generated Function request ID joins these events even when Microsoft IDs are absent or change -from header to envelope. The identifiers are intentionally not named simply `requestId` or -`correlationId` in logs: - -| Log field | Source and meaning | -|---|---| -| `functionName` | Runtime function name: `SendOtp` in JavaScript/.NET, `send_otp` in Python. The HTTP route remains `/api/SendOtp` in every runtime. | -| `functionRequestId` | Generated by this handler. Matches `requestId` in failure responses; it is not Microsoft's request ID. | -| `functionInvocationId` | Azure Functions host invocation ID. Null for direct handler calls without a host context. | -| `x-ms-client-request-id` | Raw Microsoft per-attempt ID from the header of the same name, not the generated fallback used by dispatch. | -| `x-ms-correlation-id` | Raw selected Microsoft correlation: envelope `correlationId` first, then the header of the same name. The existing precedence is unchanged, and this never contains a generated Function ID. | -| `msCorrelationIdSource` | `envelope`, `header` or `none`; disambiguates the selected value when the envelope and header differ. | -| `providerMessageId` | Raw provider-normalized message/reference ID returned by the provider, including Telesign `reference_id`, for support lookup. Not filled from delivery or Function IDs, although a provider may echo an ID it received. | -| `providerTenantId` | Raw configured `EPP_PROVIDER_TENANT_ID` for OAuth, not incoming envelope `tenantId`. | -| `functionOutboundClientId` | Raw application's `EPP_OUTBOUND_CLIENT_ID` used for provider access, not a Microsoft request ID. Guided setup reuses the endpoint app client ID here; its outbound purpose is distinct from the inbound audience setting. | -| `functionOutboundManagedIdentityClientId` | Raw configured `EPP_OUTBOUND_MI_CLIENT_ID` used to obtain the app assertion, not the Key Vault identity or the identity's principal/Object ID. | -| `providerEndpoint` | Validated final outbound URL without userinfo, query string or fragment; scheme, host/port and API path remain visible. | - -The six external/configuration ID fields preserve their raw values and case for cross-system support -lookup; they are not hashed or truncated. To avoid dumping arbitrary text, only nonblank strings -of 1-128 ASCII characters are recorded: the first character must be a letter or digit, followed by -letters, digits, `.`, `_`, `:`, or `-`. This includes GUIDs, hex IDs and ordinary opaque references. -Missing/blank values are null; other invalid values are null and their field names appear in -`omittedIdFields`, never the rejected values. These guards affect logs only, not wire IDs or outcomes. -Application/client/tenant IDs are identifiers, not client secrets or bearer tokens. -These are tracing fields, not authentication assertions. In particular, an incoming `tenantId` -does not become a trusted tenant identity in logs. The existing wire correlation precedence, -provider request IDs and public responses are unchanged. - -The fixed `request_completed` event contains only the final HTTP status, result and elapsed time. -Stage-specific fields remain on the event where they become known instead of being accumulated into -a mutable summary. `providerHttpStatus` is recorded when response headers arrive, so a response-body -timeout can legitimately produce `provider_response_received` with upstream `200` followed by -`request_failed` with Function status `504`, without a mapped provider status or success -acknowledgement. Unknown provider status text and malformed JSON are never logged; malformed JSON -emits only `provider_response_invalid_json` before the provider outcome rules run. - -Normal events and completion events use Information; invalid requests, non-success 4xx outcomes and -advisory warnings use Warning; 5xx failures and timeouts use Error. Keep application Information logs -enabled when investigating. This contract describes **emission**, not guaranteed collection: -host/telemetry filters and sampling can drop trace records. Application events are logs, not -the host's Request telemetry type, so excluding `Request` from sampling does not by itself retain -every event. Configure collection and retention deliberately without enabling SDK/body tracing. -Easy Auth rejections occur before the handler; inspect available platform/authentication diagnostics -and caller observations, not just application events. - -The camelCase/`x-ms-*` fields above describe the JavaScript schema and are **not a portable -custom-properties contract**. Python uses `event_name` with logging extras such as `httpStatus`; -.NET uses typed `ILogger` fields such as `HttpStatus` and scopes such as `FunctionRequestId`. -The simpler Python/.NET provider-result events do not expose every JavaScript field (for example, -do not assume a provider reference ID is collected). Exporters can omit extras/scopes. -Use [runtime-specific queries](MONITORING.md#runtime-specific-log-discovery) and -[collection guidance](APPLICATION-INSIGHTS.md) to verify what actually arrives. +See the [application log reference](LOGGING.md) for event order, allowlisted fields, identifier +provenance, levels, and runtime formats. Each invocation emits fixed events ending in +`request_completed`, not a mutable request summary. Evaluation emits no provider events. +Keep bodies, nonces, credentials, and raw exceptions out of every pipeline. + +Emission is not guaranteed collection. Use [runtime-specific queries](MONITORING.md#runtime-specific-log-discovery) +and [collection diagnostics](APPLICATION-INSIGHTS.md) before relying on alerts. --- diff --git a/docs/FRONTDOOR.md b/docs/FRONTDOOR.md index 817aeaa..41d8c89 100644 --- a/docs/FRONTDOOR.md +++ b/docs/FRONTDOOR.md @@ -1,24 +1,30 @@ # Optional multi-region onboarding with Azure Front Door +[Documentation](README.md) | [Single-region setup](GETTING-STARTED.md) | [Routing troubleshooting](../setup/docs/Troubleshooting.md#optional-front-door-deployments) + Use Azure Front Door when you want **one public EPP URL backed by Function Apps in multiple regions**. You configure this option manually. For a single region, start with the [five-step setup guide](GETTING-STARTED.md). -**We don't provide a Front Door deployment script.** The guided setup creates one regional endpoint, -not the multi-region setup described here. This guide covers what you need to configure and test. -Failover can still interrupt requests. +**No Front Door deployment script is supplied.** This manual design is not guided setup, +and failover can interrupt requests. + +Follow [scope](#scope-and-observed-behavior), [architecture](#architecture-one-url-multiple-origins), +[plan/capacity](#1-plan-the-regional-deployment), [origins/keys](#2-prepare-equivalent-independently-provisioned-origins), +[readiness](#3-provide-a-non-delivering-readiness-endpoint), [routing](#4-configure-front-door-manually), +[validation](#5-validate-before-manually-activating-policy), and [fire-drill](#6-regional-failover-fire-drill). +Keep [historical results](#observed-failover-results-and-limitations) separate from +your acceptance criteria; use [operations/rollback](#operations-and-rollback) after validation. ## Scope and observed behavior -We tested an isolated JavaScript deployment with Front Door Standard, two and three regional -origins, a shared encryption certificate, and a separate readiness handler that sends no messages. -The tests covered encrypted evaluation requests, rejection of invalid callers, direct-origin -access restrictions, and stopping and restarting individual Functions. +Testing used isolated JavaScript origins, Front Door Standard, two/three regions, a shared key, +and a separate non-delivering readiness handler. It covered encrypted evaluation, invalid callers, +direct-origin restrictions, and individual Function stop/restart. -**The readiness handler used in testing is not part of the released sample.** You must implement -and validate the [readiness contract below](#3-provide-a-non-delivering-readiness-endpoint) before -enabling multi-origin health probes. There is no app setting that adds this endpoint to a release ZIP. -You can adapt the design for other runtimes, but we haven't tested it with .NET or Python. +**The readiness handler is not released.** Implement and validate the +[readiness contract](#3-provide-a-non-delivering-readiness-endpoint) before enabling probes; +no setting adds it to a ZIP. .NET/Python adaptations are untested. We **did not test** live SMS/voice delivery, calls and retries from the real Entra service, a full Azure regional outage, custom domains, WAF, Private Link, or production load. Test the features your @@ -26,21 +32,15 @@ deployment needs before activating policy. ### Regional resiliency responsibility -For the Microsoft-configured baseline, Microsoft is responsible for defining, configuring, -documenting, and validating the standard regional-failover pattern and its exercise procedure. -Customers using that baseline should not have to design regional disaster recovery (DR) themselves. -Customers operate their deployed baseline: assign -owners, maintain regional capacity, credentials and keys, monitor it, and run approved drills. -Customers who replace the topology own the alternate regional-failover design and its validation. -Customer operation of the standard baseline does not transfer responsibility for designing that -baseline to the customer. - -The two-region pattern below makes the intended baseline concrete, but today's onboarding remains -single-region and Front Door setup remains manual. The readiness-handler, regional-dependency, -capacity, real-caller, and outage-validation gaps described here remain to be addressed before -relying on this pattern for regional recovery. Recovery-time and recovery-point objectives -(RTO/RPO), tolerable request failures, latency limits, and required evidence must be defined and -approved by the deployment owners; the observed results below do not establish those targets. +Microsoft owns defining, configuring, documenting, and validating its standard regional-failover +baseline and exercise procedure; customers using it should not design regional DR themselves. +Customers operate the deployed baseline: ownership, capacity, credentials/keys, monitoring, and +approved drills. Replacing the topology makes its alternate design/validation customer-owned; +operating the standard baseline does not transfer its design responsibility. + +Onboarding remains single-region and Front Door manual. Readiness, dependency isolation, capacity, +real-caller behavior, and regional-outage validation remain gaps. Deployment owners must approve +RTO/RPO, tolerable failures, latency, and required evidence; historical results do not set those targets. ## Architecture: one URL, multiple origins @@ -289,21 +289,14 @@ preserve unrelated properties, and read the policy back to confirm the change. T ### Test scenarios to plan and record -Use this checklist to agree what you will test and what counts as a pass. **These are proposed -tests, not a report that they have all been run.** The historical JavaScript evaluation, access -rejection, and individual app-stop/restart tests are described in -[scope and observed behavior](#scope-and-observed-behavior) and -[observed failover results](#observed-failover-results-and-limitations). Those results do not prove -double-load capacity, complete dependency isolation, or a full regional outage. - -Run only in an approved, isolated nonproduction environment, with synthetic data, bounded load, -and the [fire-drill's approval, abort, and restoration safeguards](#6-regional-failover-fire-drill). -Use an authorized test harness calling the Front Door endpoint, not an actual Entra ID sign-in flow. -**Function evaluation suppresses sends only inside this endpoint.** Confirm Entra ID fallback -and retry behavior with Microsoft rather than assuming a whole sign-in flow is non-delivering. -Entra ID sign-in and delivery testing require separate safety approval and are not part of this checklist. -Agree load, headroom, recovery time, error/latency limits, soak duration, and restoration deadlines -before starting; do not substitute the historical measurements for your acceptance targets. +**Proposed tests, not completed results.** Historical [app-stop trials](#observed-failover-results-and-limitations) +do not prove double-load capacity, dependency isolation, or a regional outage. +Approve synthetic data, bounded load, and the [drill's abort/restoration safeguards](#6-regional-failover-fire-drill) +in isolated nonproduction. Agree load/headroom, recovery, errors/latency, soak, and restoration deadlines first. + +Use an authorized harness, **not a real Microsoft Entra ID sign-in flow**. Evaluation suppresses +sends only inside this endpoint; caller fallback/retries can still deliver. Confirm caller behavior +with Microsoft and obtain separate approval for sign-in or delivery tests. | What to test | How and where to test safely | Expected outcome / acceptance | Evidence to keep | |---|---|---|---| diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index 6c0a8e7..c80ad5a 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -1,20 +1,15 @@ # Set up an External Phone Provider endpoint -[Sample overview and architecture](../README.md) | [Setup reference](../setup/docs/README.md) +[Documentation](README.md) | [Setup reference](../setup/docs/README.md) | [Troubleshooting](../setup/docs/Troubleshooting.md) -Connect Microsoft Entra ID to your phone provider to send one-time passwords by SMS or voice. -This External Phone Provider (EPP) sample sets up **one endpoint in one Azure region**. +Deploy **one endpoint in one Azure region** for Microsoft Entra ID SMS and voice requests. +No clone, manual package build, or second language deployment is needed. +SMS and voice can use different provider URLs on the same endpoint; validate each intended channel. -You register an application, run a guided setup script, then connect and test your provider. -An administrator activates the endpoint only after those checks pass. You do not need to clone -this repository, build a package yourself, or follow all three language guides. - -The same Function endpoint supports both SMS and voice. Each request's `channel` selects its -configured provider URL, and the SMS and voice URLs can differ. Validate every channel you intend -to activate. - -The screenshots show the Azure portal. You can also register the application -in the Microsoft Entra admin center. Identifying details are hidden; your portal layout may differ. +Follow in order: [prepare](#1-get-ready), [register](#2-register-your-application), +[deploy](#3-run-guided-setup), [connect and test](#4-connect-your-provider-and-test), +then [activate and hand over](#5-activate-and-hand-over). +Portal screenshots hide identifying details; labels and layout may differ. ## 1. Get ready @@ -27,9 +22,8 @@ Buying an offer does not enable the tenant feature or deploy this sample. If acc approved procedure is unavailable, **stop here** and follow the [access and support guidance](ONBOARDING.md#before-purchasing-or-deploying). -In Security Store, open **Private solutions** and select your approved subscription to review -its available offers. See [sample offers](ONBOARDING.md#before-purchasing-or-deploying) in the -detailed guide; a listing does not guarantee support in this setup script. +In Security Store, open **Private solutions** and select your approved subscription. +A listing does not guarantee [guided provider support](ONBOARDING.md#provider-credential-names). Use the [SMS cost estimation guide](COST-CALCULATOR.md) to create a planning estimate, then confirm destination rates, fees, and final costs with your provider. @@ -48,15 +42,13 @@ Have these ready: - **Azure region and budget:** check availability and subscription quota for your chosen Linux plan. Azure resources and provider services can incur charges. -Review the [full prerequisites](../setup/docs/README.md#prerequisites-for-step-2), including the -required Entra allowed-tenants preview, before running setup. Microsoft Graph operations reuse -the Azure CLI login; no Graph PowerShell SDK or separate Graph sign-in is required. The script -can install Bicep after confirmation; Azure CLI must already be installed. +Check the [full prerequisites](../setup/docs/README.md#prerequisites-for-step-2), including the +Microsoft Entra ID allowed-tenants preview. Graph reuses the Azure CLI login; no Graph PowerShell SDK or +separate sign-in is needed. Setup can install Bicep after confirmation, but not Azure CLI. -Choose an **onboarding owner** in your organization to coordinate the administrators, provider, -and Microsoft support. This is a sample, not production certification: it does not provide +Assign an **onboarding owner** to coordinate administrators, provider, and Microsoft support. +Review the [sample limitations](CONTRACT.md#production-limitations): no production certification, automatic send retries, duplicate-send protection, or automatic certificate renewal. -Review the [production limitations](CONTRACT.md#production-limitations) with that owner. ## 2. Register your application @@ -91,8 +83,7 @@ changing the provider or region on a rerun does not create a separate deployment ## 3. Run guided setup -Open **PowerShell 7** in a folder where you want to keep the script and deployment summary. -Download the script: +After the checks above, open **PowerShell 7** in a private working folder and download the script: ```powershell Invoke-WebRequest ` @@ -109,21 +100,16 @@ Review the downloaded script, then run it: On a shared or multi-account workstation, use `.\Setup-Epp.ps1 -ForceAuthentication` to request an explicit Azure CLI device-code sign-in. Microsoft Graph reuses that CLI session. -Setup first lists cached Azure CLI user subscriptions. Select the customer tenant and subscription, -then select or enter the dedicated application client ID and choose an Azure region. Refresh or -sign in when the expected account is missing. - -Choose the provider route and enter or confirm the provider **by name**, then choose **one** -platform: Node.js (JavaScript), .NET (C#), or Python. Setup configures both SMS and voice URLs; -there is no channel prompt. It downloads and verifies the package, builds it if needed, and -deploys it for you. You do not need a separate ZIP upload or local settings file. +Select the customer tenant/subscription, dedicated application **client ID**, and Azure region. +Refresh or sign in if the expected account is missing. Then choose: -Choose **FC1** for on-demand hosting that can scale to zero, or **EP1** for paid warm capacity. -FC1 can cold-start, and its free usage grant does not make the whole deployment free. -See [hosting plan details](../setup/docs/README.md#service-plan-selection). -In **Tenant scope**, choose Global or EU for your provider route. This does not choose your -Azure region or guarantee data residency. -For the resource prefix, use 2-8 lowercase letters or digits, starting with a letter. +- **Provider and route:** enter or confirm the provider by name. **Tenant scope** means Global/EU + provider routing, not Azure region or a data-residency guarantee. +- **One platform:** JavaScript, C#, or Python. Setup verifies, builds as needed, and publishes + the package, with both channel URLs. No channel prompt, local settings, or separate ZIP upload. +- **Hosting:** FC1 can scale to zero and cold-start; EP1 has paid warm capacity. FC1's usage grant + does not make deployment free. Review [plan details](../setup/docs/README.md#service-plan-selection). +- **Resource prefix:** 2-8 lowercase letters/digits, starting with a letter. Complete any required CLI sign-in, MFA, or administrator consent yourself. Setup checks prerequisites and prepares the deployment plan; resolve any reported failure before continuing. @@ -144,15 +130,11 @@ are separate from this approval. This still performs online preflight and downloads. There is no `-DryRun` or `-WhatIf` switch; see [plan review](../setup/docs/README.md#reviewing-the-plan-without-deploying) for details. -After you approve, leave the script running while it configures the application, creates the -Azure resources, and publishes the code. Wait for **Deployment completed** and the saved summary -before moving to Step 4. - -Setup creates the Function App, storage, Key Vault, identities, and monitoring resources. -**Do not create these resources manually first.** -Keep the public certificate and timestamped summary in `epp-output` beside the script. -Use the [summary field guide](../setup/docs/README.md#read-the-deployment-summary) to find your -resources, endpoint URL, application client ID, and encryption key ID. No private key is downloaded. +Wait for **Deployment completed** and the saved summary before Step 4. Setup creates the +Function App, storage, Key Vault, identities, and monitoring; **do not create them manually first**. +Keep `epp-output` private. Its public certificate and +[summary](../setup/docs/README.md#read-the-deployment-summary) identify resources, endpoint, +application client ID, and encryption key ID. No private key is downloaded. Setup verifies that the Function can resolve its selected private-key reference before changing the registered encryption certificates. It also verifies **Easy Auth**, the endpoint's @@ -171,15 +153,11 @@ the portal's Create button to deploy the endpoint.* ## 4. Connect your provider and test -Complete the [provider authentication instructions](ONBOARDING.md#complete-provider-authentication) -for your selected integration. This means either entering credentials in the setup-created Key Vault -or having the provider administrator authorize the application. Azure deployment alone does not -complete that step. Keep credentials out of source code, screenshots, and shared logs. - -For an API-key integration, follow the -[illustrated Key Vault steps](ONBOARDING.md#where-to-enter-api-key-credentials-in-key-vault) -using your own vault and the exact secret names required by your provider. OAuth integrations -use the provider-administrator handoff instead; do not create API-key secrets for them. +Complete [provider authentication](ONBOARDING.md#complete-provider-authentication); deployment +does not do this for you. API-key integrations use the +[setup-created vault and exact secret names](ONBOARDING.md#where-to-enter-api-key-credentials-in-key-vault). +OAuth uses the provider-administrator handoff, not API-key secrets. +Keep credentials out of source, screenshots, and shared logs. In your Function App, open **Settings > Authentication**. Check that authentication is **Enabled**, access is set to **Require authentication**, and unauthenticated requests receive @@ -232,15 +210,5 @@ Deleting Azure resources does not undo an authentication policy change. ## Find more detail -| When you need to... | Read | -| --- | --- | -| Check permissions, prompts, hosting plans, or renewal | [Setup reference](../setup/docs/README.md) | -| Find IDs, add provider credentials, or run acceptance checks | [Customer configuration and validation](ONBOARDING.md) | -| Resolve a setup or delivery problem | [Troubleshooting](../setup/docs/Troubleshooting.md) | -| Find logs and set up alerts | [Monitoring](MONITORING.md) and [Application Insights](APPLICATION-INSIGHTS.md) | -| Choose identity and configuration alerts | [Three recommended Entra monitors](SIGN-IN-MONITORING.md#recommended-monitors), with sign-in investigation and alert setup | -| Understand the architecture, settings, or packages | [Technical reference](../TECHNICAL.md) and [HTTP contract](CONTRACT.md) | -| Explore multiple Azure regions | [Manual Front Door guide](FRONTDOOR.md); not included in guided setup | -| Build a custom implementation | [JavaScript](../javascript/README.md), [C#](../dotnet/README.md), or [Python](../python/README.md) | -| Implement the Entra ID-facing BYOT interface | [BYOT request and response guide](BRING-YOUR-OWN-PROVIDER.md); caller authentication, encrypted payload, and responses, with customer-owned service integration | -| Design primary-to-secondary provider fallback | [Developer walkthrough](MULTI-PROVIDER-IMPLEMENTATION.md); not a built-in feature | +Use the [task-based documentation index](README.md) for operations, developer references, +and optional designs. For a failed step, go directly to [troubleshooting](../setup/docs/Troubleshooting.md). diff --git a/docs/LOGGING.md b/docs/LOGGING.md new file mode 100644 index 0000000..b7cf36a --- /dev/null +++ b/docs/LOGGING.md @@ -0,0 +1,129 @@ +# Application log reference + +[Documentation](README.md) | [Find deployed logs](MONITORING.md#runtime-specific-log-discovery) | [HTTP contract](CONTRACT.md) + +Use this reference when implementing or interpreting endpoint events. For an incident, start with +the monitoring walkthrough, not a search for a universal JSON request summary. + +**Never log request bodies, decrypted fields, credentials, nonces, raw exceptions, or provider +response bodies.** Keep SDK, platform, and proxy body tracing off. This page specifies emission, +not guaranteed collection. + +## Runtime formats + +| Runtime | Emission and context | +| --- | --- | +| JavaScript | JSON service records with immutable request context and existing camelCase/`x-ms-*` fields. | +| Python | Standard `logging`; `otp_log.py` defines fixed event IDs, names, levels, and fields. An immutable `LoggerAdapter` supplies Function and Microsoft trace identifiers. | +| .NET | Standard `ILogger`; `OtpLog` defines source-generated IDs/names. `ILogger.BeginScope` supplies `FunctionName`, `FunctionRequestId`, `FunctionInvocationId`, `MsClientRequestId`, `MsCorrelationId`, and `MsCorrelationIdSource`. | + +Each invocation ends with a fixed `request_completed` event, not a mutable comprehensive summary. +The configured logger/exporter owns formatting and property preservation. +The fields below describe JavaScript, **not portable custom properties**. Python uses `event_name` +and extras such as `httpStatus`; .NET uses `HttpStatus` and scopes such as `FunctionRequestId`. +Python/.NET provider-result events expose fewer fields; do not assume a provider reference ID is +collected. Verify the [deployed schema](MONITORING.md#runtime-specific-log-discovery). + +## Event sequence + +A successful live request emits these events in order: + +| Service event | Safe information recorded | +|---|---| +| `request_received` | Function invocation and available raw Microsoft trace IDs under their `x-ms-*` names; no raw body or arbitrary headers. | +| `envelope_validated` / `payload_validated` | Allowlisted body metadata: validated payload type, normalized `channel`, `evaluation` and optional `ttlSeconds`. | +| `delivery_context_decrypted` | Decryption completed; no plaintext fields, JWE or key ID. | +| `provider_selected` | Registered provider and its authentication mode. | +| `provider_credential_resolution_started` | OAuth client-assertion or Key Vault credential source, with explicitly named raw OAuth application/identity/tenant IDs. | +| `provider_credential_resolved` | Credential resolution and usability checks completed, with elapsed time; no secret, token, assertion or claims. | +| `provider_request_build_started` | The provider is preparing the outbound request. | +| `provider_request_built` | Allowlisted HTTP method, final endpoint scheme/host/port/API path, HTTPS and disabled redirects; no query string, authorization headers or body. | +| `provider_request_started` | The outbound send is beginning, with method, sanitized endpoint and timeout. | +| `provider_response_received` | Actual upstream HTTP status; emitted before response-body reading completes. | +| `provider_response_processed` | Mapped provider status/outcome, fixed failure classification and duration. Raw provider descriptions and bodies are excluded. | +| `response_prepared` | Response status and booleans indicating nonce/correlation inclusion, not their values or the response body. | +| `request_completed` | Final HTTP status, result, and elapsed time; stage-specific fields stay on their events. | + +JavaScript uses `envelope_validated`; Python/.NET use `payload_validated`. +Evaluation skips provider configuration, credentials, HTTP, and provider events. It emits +`evaluation_completed`, then `response_prepared` and `request_completed`. + +Failures emit `request_failed` with fixed stage, reason, and HTTP status (`FailureStage`, +`FailureReason`, `HttpStatus` in .NET). Provider results retain coarse `Outcome` and a safe +classification: `provider_http_error`, `provider_rejected`, `unrecognized_provider_status`, +`missing_provider_message_id`, or `invalid_provider_json`. `provider_response_processed` includes +that classification, upstream HTTP status, and normalized status, never descriptions/bodies. +Credential acquisition/refresh failures use `credential_refresh_failed`; unexpected failures use +`unexpected_error`. No event adds credential reads, acquisition, or send retries. + +`response_prepared` runs on success **and failure** before the handler returns. It does not prove +host serialization/transmission or caller receipt; use platform request telemetry for transport +completion. A parsed provider rejection uses `provider_response_processed` with a non-success +outcome and fixed reason. + +`providerHttpStatus` is recorded when response headers arrive. +An upstream `200` can precede a body-read timeout: `provider_response_received` may be followed by +`request_failed` with Function `504`, without mapped provider status or success acknowledgement. +Unknown status text and malformed JSON are never logged; malformed JSON emits only +`provider_response_invalid_json` before provider outcome rules run. + +## Safe fields and identifiers + +Build metadata from **validated allowlisted fields**, not a body dump with sensitive properties +removed. Unknown properties, `tenantId`, locale/risk data, and decrypted fields stay excluded. +Invalid envelopes never contribute unvalidated type or TTL values. + +`providerCredentialSource` is `managed_identity_client_assertion` for app tokens and `key_vault` +for API keys. Cached resolution does not imply a network fetch or fresh token exchange. +`providerCredentialElapsedMs` includes resolution and usability checks, not a separate deadline. +SDK diagnostics remain suppressed. + +`provider_request_built` describes the outbound request, not a new physical connection or HTTP +client. `providerEndpoint` records the **final** scheme, host/port, and path, which can differ +from the configured base URL. For example, `https://provider.example/epp/voice?key=secret` becomes +`https://provider.example/epp/voice`. Userinfo, query strings, and fragments are excluded, but +**path segments are not redacted**: use provider-approved paths without secrets/personal data. +Logging does not alter the actual URL or required query parameters. Standard verbs are uppercase; +custom/invalid verbs appear as `other` without changing the sent request. + +JavaScript/Python events carry Function request/invocation IDs and Microsoft trace IDs available +at that stage. JavaScript also carries known channel/evaluation/provider context; Python uses +its adapter and event arguments. Initially only header IDs are available; a valid envelope can +later supply the selected correlation. The generated Function request ID joins events even when +Microsoft IDs are absent or change. Log names deliberately distinguish these sources: + +| Log field | Source and meaning | +|---|---| +| `functionName` | Runtime function name: `SendOtp` in JavaScript/.NET, `send_otp` in Python. The HTTP route remains `/api/SendOtp` in every runtime. | +| `functionRequestId` | Generated by this handler. Matches `requestId` in failure responses; it is not Microsoft's request ID. | +| `functionInvocationId` | Azure Functions host invocation ID. Null for direct handler calls without a host context. | +| `x-ms-client-request-id` | Raw Microsoft per-attempt ID from the header of the same name, not the generated fallback used by dispatch. | +| `x-ms-correlation-id` | Raw selected Microsoft correlation: envelope `correlationId` first, then the header of the same name. The existing precedence is unchanged, and this never contains a generated Function ID. | +| `msCorrelationIdSource` | `envelope`, `header` or `none`; disambiguates the selected value when the envelope and header differ. | +| `providerMessageId` | Raw provider-normalized message/reference ID returned by the provider, including Telesign `reference_id`, for support lookup. Not filled from delivery or Function IDs, although a provider may echo an ID it received. | +| `providerTenantId` | Raw configured `EPP_PROVIDER_TENANT_ID` for OAuth, not incoming envelope `tenantId`. | +| `functionOutboundClientId` | Raw application's `EPP_OUTBOUND_CLIENT_ID` used for provider access, not a Microsoft request ID. Guided setup reuses the endpoint app client ID here; its outbound purpose is distinct from the inbound audience setting. | +| `functionOutboundManagedIdentityClientId` | Raw configured `EPP_OUTBOUND_MI_CLIENT_ID` used to obtain the app assertion, not the Key Vault identity or the identity's principal/Object ID. | +| `providerEndpoint` | Validated final outbound URL without userinfo, query string or fragment; scheme, host/port and API path remain visible. | + +The six external/configuration ID fields preserve raw values and case, without hashing/truncation. +Only nonblank strings of **1-128 ASCII characters** are recorded: a letter/digit first, then +letters, digits, `.`, `_`, `:`, or `-`. Missing/blank values are null; other invalid values are +null and their names appear in `omittedIdFields`, never their values. +These guards affect logs only, not wire IDs, correlation precedence, or outcomes. +Application/client/tenant IDs are identifiers, not secrets or trust assertions; incoming +`tenantId` never becomes a trusted identity. + +## Levels and collection + +Normal/completion events use **Information**; invalid requests, non-success 4xx outcomes, and +advisories use **Warning**; 5xx failures/timeouts use **Error**. +Keep Information logs enabled for investigation without enabling SDK/body tracing. + +Filters and sampling can remove events. Application events are logs, not host Request telemetry; +excluding `Request` from sampling does not retain every event. Configure collection and retention +deliberately. Easy Auth rejection happens before the handler: inspect platform/authentication +diagnostics and caller observations rather than expecting an application event. + +Next: [verify collection](APPLICATION-INSIGHTS.md#single-region-configuration), then use +[runtime-specific queries](MONITORING.md#runtime-specific-log-discovery). diff --git a/docs/MONITORING.md b/docs/MONITORING.md index c4d08b5..938c4da 100644 --- a/docs/MONITORING.md +++ b/docs/MONITORING.md @@ -1,5 +1,7 @@ # Monitoring setup and sample queries +[Documentation](README.md) | [Collection diagnostics](APPLICATION-INSIGHTS.md) | [Log schema](LOGGING.md) | [Endpoint troubleshooting](../setup/docs/Troubleshooting.md#after-deployment-triage) + Use this guide to find your endpoint's logs and set up alerts. Guided setup creates Application Insights and a Log Analytics workspace, but **you still need to configure and test notifications**. @@ -16,9 +18,10 @@ For your first single-region deployment: 3. [Set up and test alerts](#5-set-up-notifications-and-alert-rules), including certificate reminders and cost ownership. -The multi-region queries, shared Workbook, and Front Door sections are optional. -You do not need them to find single-region logs. For collection and identity details, see -[Application Insights](APPLICATION-INSIGHTS.md). +For **missing data**, use [collection checks](#9-sampling-ingestion-gaps-privacy-and-validation). +Optional: [multiple regions](#3-multiple-regions-and-cross-workspace-queries), +[Workbook](#6-build-a-shared-operations-workbook), [availability checks](#7-availability-authenticated-functions-and-fc1), +and [Front Door](#8-optional-azure-front-door-layer). None is needed to find single-region logs. These examples do not create monitors automatically. Review permissions, privacy, and charges before applying them. Thresholds and intervals are **starting points, not SLAs**; tune them to @@ -58,9 +61,8 @@ The template requests 30-day retention; check effective workspace/table retentio ![Application Insights Overview showing the Logs workspace link with account and connection details redacted](images/onboarding/monitoring-workspace.png) -These are real portal views of an existing test deployment, with identifying information and -connection details permanently hidden. Resource names, regions, and portal layouts may differ. -The links show where to navigate; they do not prove that test traffic or delivery succeeded. +These test-deployment screenshots hide identifiers and connection details. Names, regions, and +layout may differ; navigation links do not prove successful traffic or delivery. Keep connection strings and credentials private. `AppRequests`/`AppTraces` are not interchangeable with `FunctionAppLogs` or `AzureDiagnostics`. @@ -86,10 +88,8 @@ AppRequests | order by LastSeen desc ``` -Read access is required on every workspace queried. Give operators only the -necessary resource/workspace access; saving a Workbook does not grant its readers -access to its data. Cross-workspace alert evaluation also needs access to each -workspace under the rule's configured identity. +Grant only required read access on **each workspace**. Workbook sharing does not grant data access; +alert evaluation needs its own identity's access, not the author's. ### Run a first query and read the results @@ -144,16 +144,10 @@ AppRequests | order by TimeGenerated asc ``` -`ItemCount` accounts for the telemetry items represented by a sampled record. -Weighted counts and percentiles remain estimates when sampling is active; they -cannot recover telemetry that was never ingested. Empty buckets are **no data**, -not zero failures or 100% availability. `Success` is the instrumented request -outcome, not evidence that a phone received an OTP. - -Both evaluation and live calls can return HTTP 200. These request-table statistics -combine them unless your deployed instrumentation provides a verified classification. -Use the JavaScript service-event query below for a separate view; do not infer -live delivery from the HTTP status. +`ItemCount` weights sampled records; counts/percentiles remain estimates, not recovery of lost data. +Empty buckets mean **no data**, not zero failures or availability. `Success` is a request outcome, +not handset receipt. HTTP 200 includes both evaluation and live calls; use +[classified service events](#separate-evaluated-accepted-and-failed-completions) where verified. ### Failure status breakdown @@ -396,12 +390,11 @@ collection before creating alerts. Missing events require investigation, not a h .NET host OpenTelemetry does not initialize a direct worker exporter, and malformed .NET binding can fail before handler events. See [collection configuration](APPLICATION-INSIGHTS.md#what-the-checked-in-sample-configures). -For an incident, use the host `OperationId`/invocation ID and whichever application request ID -is actually exported. Keep raw Microsoft/provider support IDs distinct; not every runtime -exports the provider reference. Never use OTPs/nonces or message bodies as correlation keys. -Look for a correlated `request_failed` event's stage/reason and endpoint status, then the -separate provider-response event's upstream status. A provider `200` can precede a body-read -timeout and Function `504`; it is not necessarily a successful invocation. +For incidents, correlate `request_failed` stage/reason/status with provider-response events. +Use the exported host `OperationId`/invocation and application request IDs, not OTPs, nonces, or bodies. +Keep Microsoft/provider IDs distinct; some runtimes omit provider references. +An upstream `200` can precede a body-read timeout and Function `504`. +See [event order and identifier provenance](LOGGING.md). `credential_refresh_failed` also differs operationally: JavaScript/Python have a polling refresh loop, while .NET emits this event on failed startup/cache-fill acquisition and has no @@ -411,22 +404,17 @@ health check. ## 5. Set up notifications and alert rules -Have an operator review and create these resources in a nonproduction environment -first. Rule creation requires appropriate write permissions; data access alone is -not enough. Agree on ownership, severity, escalation, and maintenance handling. +Start in nonproduction with an operator who has rule-write permissions, not just data access. +Agree ownership, severity, escalation, and maintenance handling. ### Action groups -In **Azure Monitor > Alerts > Action groups > Create**, select the resource group, -name, and processing region appropriate to your residency policy. Add an on-call -email receiver and, if required, a secured incident-management integration. Prefer -a notification path that does not depend on the OTP provider being monitored. -Use the common alert schema where supported and send a test notification before -attaching production rules. A test notification verifies routing, not that an -alert condition actually evaluates correctly. +In **Azure Monitor > Alerts > Action groups > Create**, choose the resource group, name, and +residency-appropriate processing region. Add on-call email and any secured incident integration; +avoid depending on the monitored OTP provider. Use the common alert schema where supported. +Test notification routing before attaching production rules; that test does not verify the alert query. -For multi-region deployments, decide whether the action group should use global -or supported regional processing, and ensure an incident in one app region cannot +For multiple regions, choose global/supported regional processing so one failed app region cannot disable the only response path. See [action group setup][action-groups]. ### Metric alerts @@ -530,17 +518,13 @@ Add query panels using the correct workspace scope: that parser is verified; otherwise show the runtime-specific validated view. 5. Relevant Function/dependency metric charts, current alerts, and deployment context. -For one region, select one workspace/component/role. For several regions, use -explicit workspace sources or a carefully scoped shared workspace and retain the -regional breakdown. Add time/resource parameters for interactive investigation; -replace hardcoded query windows when wiring a time parameter so it actually -controls every source. Keep scheduled-alert queries independent of Workbook-only -parameters. Do not color an empty panel green. - -Save to an appropriate resource group, grant Workbook access to the operations -team, and verify readers also have data access. Keep private resource identifiers, -query exports, and screenshots out of public documentation. See -[Workbook authoring][workbooks]. +Scope to the chosen workspace/component/role; multiple regions need explicit sources or scoped +shared-workspace queries with regional grouping. Bind time/resource parameters into every source, +replacing hardcoded windows. Scheduled alerts cannot depend on Workbook-only parameters. +**Do not color an empty panel green.** + +Save to the approved resource group and verify both Workbook and data access for readers. +Keep identifiers, exports, and screenshots private. See [Workbook authoring][workbooks]. ## 7. Availability, authenticated Functions, and FC1 diff --git a/docs/MULTI-PROVIDER-IMPLEMENTATION.md b/docs/MULTI-PROVIDER-IMPLEMENTATION.md index 0828d1a..e1241b2 100644 --- a/docs/MULTI-PROVIDER-IMPLEMENTATION.md +++ b/docs/MULTI-PROVIDER-IMPLEMENTATION.md @@ -1,18 +1,22 @@ # Add conservative primary-to-secondary provider fallback -You can adapt this application to try a secondary provider **only when the primary is known -not to have accepted the message**. Keep one URL for Microsoft Entra ID, `/api/SendOtp`, with a primary -account and a secondary account for the same SMS channel. You choose their order according to -your approved account policy; these roles do not imply a vendor ranking or recommendation. -The caller does not select an account or supply a provider header. +[Documentation](README.md) | [Normal deployment](GETTING-STARTED.md) | [Provider interface](BRING-YOUR-OWN-PROVIDER.md) -The sample still ships with one provider per deployment and **no provider fallback**. This guide -describes customer code you must implement and review. A timeout, lost response or generic -provider error is not proof of nonacceptance: in those cases, do not automatically send again. +**The sample has no provider fallback.** This walkthrough requires customer code, durable state, +provider-specific approval, and separate testing. Keep one `/api/SendOtp` URL for Microsoft Entra ID +and a server-owned primary/secondary order for the same SMS channel, not caller-selected accounts +or a vendor ranking. -For a normal deployment, follow the [five-step setup guide](GETTING-STARTED.md) instead. This is an advanced -developer walkthrough: it requires custom code, durable operation state, provider-specific -approval, and separate testing. It is not another step in guided onboarding. +Try a secondary **only with proof that the primary did not accept**. Timeouts, lost responses, +and generic errors are not that proof; never automatically resend on those signals. + +Follow [policy](#1-set-a-fixed-provider-order-and-a-deny-by-default-policy), +[account configuration](#2-configure-and-prevalidate-both-accounts), +[credential isolation](#3-give-each-context-its-own-credential-service-and-lifecycle), +[durable state](#4-add-durable-operation-state-before-enabling-fallback), +[orchestration](#5-integrate-one-guarded-fallback-into-the-handler), +[deadline](#6-enforce-one-aggregate-deadline), and [validation](#7-validate-offline-then-deploy-only-after-separate-approval). +[.NET/Python pointers](#net-and-python-integration-pointers) are not implemented fallback. ## 1. Set a fixed provider order and a deny-by-default policy @@ -307,16 +311,13 @@ store faults; block external network. Verify: - Evaluation returns before operation claims/provider calls; incomplete/decryption failures remain errors. Credentials, endpoints and wire responses stay isolated between accounts. -An offline policy-model experiment can check those branches, but a fake classifier does not -prove any real provider's nonacceptance semantics, and a fake store does not prove distributed -durability. Routing/isolation tests alone are **not fallback tests**. No production -nonacceptance classifier is supplied here; keep response-based fallback disabled until reviewed. +Fake classifiers/stores cannot prove provider nonacceptance or distributed durability. +Routing/isolation tests alone are **not fallback tests**. No production classifier is supplied; +keep response-based fallback disabled until reviewed. -Use one Function App/package and the existing `/api/SendOtp` entry point. Deploy new code for the -orchestrator, classifier, durable-store integration or code-owned policy changes. Validate -startup-loaded configuration changes and perform a controlled restart. For secret rotation, -verify cache refresh; update pinned references explicitly. Repeating an unchanged approved test -or editing Markdown needs no redeployment. +Deploy one app/package at `/api/SendOtp` for code-owned policy, orchestrator, classifier, or store changes. +Validate startup configuration on controlled restart. Verify cache refresh for rotation and update +pinned references explicitly. Unchanged approved tests or Markdown edits need no redeployment. Preserve inbound authentication and origin/network restrictions, including any Front Door forwarding. Regional failover is separate from provider fallback. Before live use, review diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index bb6a5cb..333063f 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -1,20 +1,18 @@ # Customer configuration and validation -Start with the [five-step setup guide](GETTING-STARTED.md). Come here when you need exact IDs, -provider credential names, or the checks to complete before activation. +[Documentation](README.md) | [First deployment](GETTING-STARTED.md) | [Troubleshooting](../setup/docs/Troubleshooting.md) -**After successful guided setup, go straight to [provider authentication](#complete-provider-authentication), -then [validate the endpoint](#validate-the-deployed-endpoint).** The script has already published -the code and configured Azure. You do not need local settings, Core Tools, or a second deployment. +**After guided setup:** complete [provider authentication](#complete-provider-authentication), +then [endpoint validation](#validate-the-deployed-endpoint). Azure and code are already deployed; +no local settings, Core Tools, or second deployment is needed. -For earlier steps, use [access requirements](#before-purchasing-or-deploying), -the [values worksheet](#values-and-ownership), or the [setup reference](../setup/docs/README.md). -[Local development and manual deployment](#optional-local-development-and-manual-deployment) -are separate, optional paths. - -For operational handover, use [Function monitoring](MONITORING.md) and, separately, -[Microsoft Entra sign-in monitoring](SIGN-IN-MONITORING.md) for authentication outcomes -and potential abuse investigations. Endpoint setup does not enable tenant log export or alerts. +| Task | Go to | +| --- | --- | +| Confirm access before spending | [Eligibility and approved procedures](#before-purchasing-or-deploying) | +| Find or check setup values | [Inputs](#inputs-you-supply-to-setup), [setup-managed values](#values-setup-configures-for-you) | +| Connect the provider | [Credential names and handoff](#complete-provider-authentication) | +| Test and activate | [Acceptance checks](#validate-the-deployed-endpoint), [operations handover](#activate-operate-and-retire) | +| Build a custom package | [Optional local/manual path](#optional-local-development-and-manual-deployment) | ## Before purchasing or deploying @@ -38,15 +36,13 @@ offer, preview, authorized test caller, or activation procedure is unavailable, and use the offer's available support route or your organization's Microsoft support channel. Do not assume that being an Azure subscription owner grants access to these features. -In these docs, **onboarding owner** means the customer-designated person coordinating the -tenant/policy administrator, Azure operator, provider administrator, and Microsoft support. -It is not the name of a contact supplied by this repository. Identify those owners and obtain -the approved test and policy-change procedures before deployment. +Assign an **onboarding owner** in your organization to coordinate tenant/policy, Azure, and +provider administrators with Microsoft support. Obtain approved test and policy-change procedures +before deployment; the repository supplies no named onboarding contact. -Read the [production limitations](CONTRACT.md#production-limitations): this sample waits for -provider acceptance, has no durable delivery queue, automatic send retries, deduplication, or -whole-invocation deadline, and uses one manually renewed decryption key. Caller fallback after -a timeout can duplicate an already accepted send. Passing setup is not production certification. +Review [production limitations](CONTRACT.md#production-limitations): no durable queue, automatic +send retries, deduplication, or whole-invocation deadline; one manually renewed decryption key. +Acceptance is not delivery, timeout fallback can duplicate sends, and setup is not certification. ## Values and ownership @@ -70,12 +66,10 @@ same parameters. No parameter accepts a provider API key or private key. | Existing certificate location | Azure portal > Key Vault > Certificates > certificate > exact version > Certificate Identifier. Use the `/certificates/` URL, not Show Secret Value. | `-KeyVaultCertificateId`; reuse only. Add `-KeyVaultSubscriptionId` when that vault is in a different subscription in the same customer tenant. | | Output directory / versions | A private local folder; optionally a reviewed source commit and published release tag. | `-OutputDirectory`, `-SourceRef`, `-PackageReleaseTag`; optional. Keep launcher and support source aligned. | -**Global/EU is provider-routing metadata, not Azure placement or a data-residency guarantee.** -The checked-in [Telesign](../setup/providers/telesign.json) and -[Soprano](../setup/providers/soprano.json) profiles currently use the same URL for Global and EU; -Soprano also uses the same API app ID/scope. The label alone does not establish where messages, -credentials, logs, or personal data are processed. Obtain account-level routing and residency -assurances from the provider and separately choose Azure resource/telemetry locations. +**Global/EU is routing metadata, not Azure placement or a residency guarantee.** +[Telesign](../setup/providers/telesign.json) and [Soprano](../setup/providers/soprano.json) currently +use identical Global/EU URLs; Soprano also shares its app ID/scope. Confirm provider processing +locations for messages, credentials, logs, and personal data; choose Azure/telemetry locations separately. **One guided deployment has one provider and accepts both SMS and voice.** Enabling the second channel does not require another setup run; validate and activate each intended channel separately. @@ -175,26 +169,20 @@ checks in the provider-specific steps below. ### Telesign: enter and verify the two vault secrets -1. Obtain the raw API key and matching Customer ID from your Telesign account's approved secure - credential process. Confirm that this account can call the configured EPP URL for each channel you intend to enable. -2. Open the **exact Key Vault in `resources.keyVault`** in the deployment summary. In - **Objects > Secrets > Generate/Import**, choose manual entry. Create `telesign-api-key` with - the raw API key, then `telesign-customer-id` with the matching raw Customer ID. - Do not base64-encode the pair, decode the key, or enter an Authorization header; the adapter - constructs Basic authentication. Keep both secrets enabled with validity dates appropriate - to your provider agreement. -3. Verify names, enabled state, and version metadata without selecting **Show Secret Value**. - In **Access control (IAM)** confirm the Function's **system-assigned identity** has Key Vault - Secrets User at this vault. Setup already assigns this and gives its Azure operator Key Vault - Secrets Officer. A different credential administrator needs separately approved vault access; - Entra administrator status alone does not grant vault data access. -4. Allow RBAC propagation and check vault network access if writes/reads fail. Do not replace - the encryption certificate secret, grant broad access, or put credentials into Function - settings to bypass a vault failure. -5. Follow the authorized evaluation and controlled live checks below. Evaluation cannot validate - these secrets. Credential caching depends on your settings: setup disables it for FC1 and - enables it for EP1. Use the [runtime cache behavior](CONTRACT.md#credential-caching-and-refresh) - to plan first-use and rotation checks. Absence of a warning does not prove account authorization. +1. Confirm account access to each intended channel's EPP URL. Use the + [vault steps above](#where-to-enter-api-key-credentials-in-key-vault) to store the raw API key as + `telesign-api-key` and matching raw Customer ID as `telesign-customer-id`. + Do not encode the pair, decode the key, or enter an Authorization header; the adapter builds Basic auth. +2. Verify enabled state, validity, and version metadata without **Show Secret Value**. + In **Access control (IAM)**, confirm the Function's **system-assigned identity** has + **Key Vault Secrets User**. Setup gives its Azure operator **Key Vault Secrets Officer**; + another credential administrator needs separate vault access, regardless of directory role. + For failures, allow RBAC propagation and check networking; never replace the encryption secret, + grant broad access, or copy credentials into app settings to bypass a denial. +3. Complete the [authorized evaluation and live checks](#validate-the-deployed-endpoint). + Evaluation does not validate these secrets. Setup disables provider caching for FC1 and enables + it for EP1; plan first-use/rotation checks from the [cache contract](CONTRACT.md#credential-caching-and-refresh). + No warning is not proof of account authorization. Use portal secret entry rather than command-line literal values, transcripts, source files, screenshots, or chat. During rotation, coordinate the matching pair and provider validity window; @@ -203,10 +191,8 @@ where the provider supports it, and verify live delivery before retiring them. ### Soprano: provider-administrator handoff -Setup configures the customer-side multitenant app and a federated identity credential trusting -the outbound managed identity. It does **not** provision your commercial account or grant -application access in Soprano's tenant. A successful Azure deployment is therefore not evidence -that Soprano OAuth is ready. +Setup creates customer-side federation, **not** the commercial account or access in Soprano's +tenant. Complete this handoff before treating OAuth as ready. Send the following **identifiers only** through the provider's approved onboarding channel: customer tenant ID; `applicationId` / `EPP_OUTBOUND_CLIENT_ID`; intended channels and their full provider URLs; @@ -284,18 +270,14 @@ Only after the acceptance checks pass, have the policy administrator follow Public Graph SMS/voice schemas do not document the EPP `url`/`appId` update. Obtain the product-approved procedure; no guessed PATCH body is supplied here. -Open **Function App > Application Insights** to locate the associated component, then its linked -**Log Analytics workspace > Logs** for the workspace-schema queries in -[MONITORING.md](MONITORING.md). Start with request discovery, then inspect the runtime-specific -events; do not search every language for `logType: "request"`. Current implementations use -`request_completed` service/log events, not a universal JSON summary. +For handover, [find the linked monitoring resources](MONITORING.md#find-the-linked-resources-in-the-portal), +discover requests, then use [your runtime's event schema](MONITORING.md#runtime-specific-log-discovery). +There is no universal JSON request summary; operation, invocation, and application IDs differ. +Easy Auth can reject before application logs exist; `response_prepared` does not prove caller receipt. -Use the [Application Insights guide](APPLICATION-INSIGHTS.md) for collection/identity/sampling, -[runtime log queries](MONITORING.md#runtime-specific-log-discovery) for Python/.NET as well as -JavaScript, and [troubleshooting](../setup/docs/Troubleshooting.md#after-deployment-triage) when -data or delivery is missing. Correlate the host operation/invocation and application request IDs -without treating them as interchangeable. Easy Auth can reject a call before application logs -exist. A `response_prepared` event does not prove the caller received the response. +Use [collection diagnostics](APPLICATION-INSIGHTS.md) for missing data and +[endpoint triage](../setup/docs/Troubleshooting.md#after-deployment-triage) for failures. +[Identity monitoring](SIGN-IN-MONITORING.md) is separate; setup does not export tenant logs or create alerts. Assign ongoing owners for request failures/latency, provider account and credential rotation, certificate expiry/renewal, alert routing, telemetry gaps, retention/cost, and policy rollback. @@ -307,11 +289,10 @@ package/source version, and renewal dates in restricted operational storage. ## Optional local development and manual deployment -**Skip this section after guided setup unless developing a custom package.** Follow the selected +**Skip after guided setup unless customizing code.** Follow the [JavaScript](../javascript/README.md), [Python](../python/README.md), or [.NET](../dotnet/README.md) -guide. Copy [local.settings.sample.json](local.settings.sample.json) to `local.settings.json` -beside that runtime's `host.json`, replace placeholders, and keep every `Values` entry a string. -Use `node`, `python`, or `dotnet-isolated` for `FUNCTIONS_WORKER_RUNTIME`. +local setup. Copy [sample settings](local.settings.sample.json) beside that runtime's `host.json`; +replace placeholders, use string `Values`, and select `node`, `python`, or `dotnet-isolated`. `UseDevelopmentStorage=true` needs local Azurite. Use a local **test** PEM/base64 PEM for `EPP_DECRYPTION_KEY_PEM`; Core Tools cannot resolve Azure Key Vault references. For evaluation-only diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..654a640 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,44 @@ +# Documentation + +[Sample overview and architecture](../README.md) + +**New deployment? Start with [Get started](GETTING-STARTED.md).** Confirm supported access and +Microsoft's approved test/activation procedure before spending or deploying. Use a dedicated +nonproduction environment; deployment does not activate policy. + +## Deploy and validate + +| Task | Guide | +| --- | --- | +| Register an app, deploy, test, and hand over | [Five-step setup](GETTING-STARTED.md) | +| Find IDs and complete provider authentication | [Configuration and validation](ONBOARDING.md) | +| Check permissions, script choices, or deployment output | [Setup reference](../setup/docs/README.md) | +| Resolve a setup or endpoint failure | [Troubleshooting](../setup/docs/Troubleshooting.md) | +| Estimate SMS/voice provider charges | [Cost estimate](COST-CALCULATOR.md); not a billing report | + +## Operate + +- [Find Function logs and create alerts](MONITORING.md); [diagnose collection](APPLICATION-INSIGHTS.md). +- [Investigate Microsoft Entra ID sign-ins and configure identity alerts](SIGN-IN-MONITORING.md). +- [Renew encryption certificates](../setup/docs/README.md#encryption-certificate-lifecycle) and + [roll back or decommission](../setup/docs/README.md#rollback-and-decommissioning). + +## Develop + +- Local setup and source: [JavaScript](../javascript/README.md), [C#](../dotnet/README.md), [Python](../python/README.md). +- [Settings and packages](../TECHNICAL.md), [HTTP contract](CONTRACT.md), and [application log schema](LOGGING.md). +- [Implement the provider-neutral BYOT interface](BRING-YOUR-OWN-PROVIDER.md). +- [Review sample limitations](CONTRACT.md#production-limitations) before production use. + +## Optional designs + +[Multi-region Front Door](FRONTDOOR.md) and [primary-to-secondary provider fallback](MULTI-PROVIDER-IMPLEMENTATION.md) +require separate implementation and validation. Neither is enabled by guided setup. + +## Get help + +For access, approved testing, or activation, use the +[provider/Microsoft support route](ONBOARDING.md#before-purchasing-or-deploying). +For setup or runtime problems, start with the [troubleshooting routes](../setup/docs/Troubleshooting.md). +Keep credentials, phone data, and raw payloads out of support requests; use +[safe diagnostic evidence](../setup/docs/Troubleshooting.md#after-deployment-triage). diff --git a/docs/SIGN-IN-MONITORING.md b/docs/SIGN-IN-MONITORING.md index 31fb9bb..b1c663e 100644 --- a/docs/SIGN-IN-MONITORING.md +++ b/docs/SIGN-IN-MONITORING.md @@ -1,9 +1,16 @@ # Monitor Microsoft Entra sign-ins -For ongoing monitoring, start with the [three recommendations below](#recommended-monitors). -To look up one failed sign-in, go to [Step 1](#1-choose-your-tenant-and-access). -The six-step walkthrough creates a user sign-in alert; the [optional reference](#optional-reference) -helps your query maintainer adapt it to the other monitors. +[Documentation](README.md) | [Function monitoring](MONITORING.md) + +| Task | Start here | +| --- | --- | +| Investigate one sign-in without creating resources | [Choose tenant/access](#1-choose-your-tenant-and-access), then [find the sign-in](#2-find-one-sign-in) | +| Set up ongoing identity alerts | [Recommended monitors](#recommended-monitors), then [export](#3-export-logs-for-ongoing-monitoring) and follow Steps 4-6 | +| Diagnose missing records or quiet alerts | [Check data](#4-check-your-data-and-choose-a-query), [monitor ingestion](#monitor-ingestion-separately) | +| Adapt a query or add advanced alerts | [Optional reference](#optional-reference) | + +The six-step walkthrough creates one user sign-in alert. Use its baseline and measurement only +for that monitor; the other recommendations need their own scoped queries. **Scope: Entra authentication and configuration, not Function or phone delivery.** Keep identity details private. EPP setup does not export these logs or create alerts; @@ -11,9 +18,8 @@ the steps below are actions for your administrators, not changes made by this gu ## Recommended monitors -Put these in place in priority order. First agree on the source tenant, exact integration -identities and target resources, and who receives each notification. Use IDs, not name -substrings or every application in the tenant. +Agree source tenant, exact identity/resource IDs, and notification owners, then implement in +priority order. Do not scope by name fragments or every tenant application. ### Priority 1: Integration identities cannot sign in @@ -60,13 +66,10 @@ substrings or every application in the tenant. Conditional Access or MFA steps do not make the overall sign-in successful. Escalate to security only with corroborating signals, not a nonzero code alone. -**Set up once, then adapt.** Follow [export setup](#3-export-logs-for-ongoing-monitoring), -select the [categories for these monitors](#choose-additional-export-categories), and use -the [field checks](#field-checks-for-the-recommended-monitors) before adapting a query. -Reuse the [alert settings](#5-create-your-first-alert) and [safe test/handover](#6-test-then-hand-over); -the walkthrough's user-failure query and measurement are not interchangeable with the -other monitors. Choose thresholds from representative ordinary traffic, not a small export -or a fixed production SLA. +**Set up once, then adapt:** select the [required categories](#choose-additional-export-categories), +verify [fields](#field-checks-for-the-recommended-monitors), and reuse the +[alert setup](#5-create-your-first-alert) and [safe test](#6-test-then-hand-over). +Choose each monitor's threshold from representative traffic, not a small export or a universal SLA. ## 1. Choose your tenant and access @@ -109,9 +112,8 @@ or a fixed production SLA. If the issue points to the endpoint or provider, follow the [Function monitoring guide](MONITORING.md#runtime-specific-log-discovery) separately. -**Only investigating one problem? You can stop here.** You don't need to export logs, create -a workspace, or incur additional Azure Monitor collection costs for this portal-only path. -Continue if your team wants ongoing alerts or longer-term analysis. +**For a single investigation, stop here.** This portal-only path needs no workspace/export +or additional Azure Monitor collection costs. Continue for ongoing alerts or longer-term analysis. ## 3. Export logs for ongoing monitoring @@ -237,39 +239,37 @@ IP-based detection for [later](#more-alert-types), once you understand this base Leave a teammate the query, rule, workspace, action group, settings, and support contacts. Arrange a check for [missing logs](#monitor-ingestion-separately), not just sign-in failures. -Your first alert is ready once these checks pass. Use the optional reference below when -you need more advanced monitoring. +Enable the ongoing alert only after these checks pass. Advanced options follow below. ## Optional reference -Use these sections when you need more than the first alert: [tenant setup and access](#1-choose-the-tenant-and-permissions), -[monitor field checks](#field-checks-for-the-recommended-monitors), [query details](#4-establish-the-baseline), -[other alert types](#more-alert-types), -[collection gaps](#monitor-ingestion-separately), [Workbooks](#6-build-a-sign-in-workbook), -and [triage, retention, and costs](#7-triage-privacy-retention-and-costs). +For query maintainers: [access/licensing](#tenant-setup-access-and-licensing), +[external tenants](#prepare-the-external-tenant-access-path), [categories](#choose-additional-export-categories), +[schema](#inspect-schema-and-arrival-delays), [field checks](#field-checks-for-the-recommended-monitors), +[query adaptation](#adapt-official-queries), [rule options](#query-identity-and-rule-options), +[other alerts](#more-alert-types), [safe testing](#validate-without-generating-provider-sends), +[Workbooks](#build-a-workbook-when-you-need-a-shared-view), and [triage/costs](#investigate-alerts-and-manage-the-data). ### Understand the evidence -1. Counts represent **stored sign-in records**, not unique customers, completed sessions, - or sends. Several records can describe one authentication sequence. A successful sign-in - can use an existing token claim without a phone challenge. -2. Invalid passwords, interrupted challenges, cancellation, and policy rejection can all - cause non-success results. Neither an SMS/Voice label nor an MFA requirement identifies - the provider. `SigninLogs` has no guaranteed EPP delivery-mode, provider-attribution, - phone-delivery, or provider-cost field. -3. Use [Function request queries](MONITORING.md#2-single-region-function-queries) and - [Application Insights](APPLICATION-INSIGHTS.md) for endpoint execution. Use the provider's - delivery and billing reports for recipient delivery and charges. Evaluation and live - Function requests can both return HTTP 200; neither proves handset receipt. +Counts are **stored records**, not unique customers, sessions, or sends. One authentication can +produce several records or succeed from an existing token claim without a phone challenge. +Non-success can mean an invalid password, interruption, cancellation, or policy rejection. +SMS/Voice/MFA labels do not identify the provider; `SigninLogs` guarantees no EPP mode, +provider-attribution, delivery, or cost field. + +Use [Function queries](MONITORING.md#2-single-region-function-queries) for execution, and provider +reports for delivery/billing. Evaluation and live requests can both return HTTP 200; neither proves receipt. ### Tenant setup, access, and licensing -1. Entra roles and Azure RBAC are separate. **Reports Reader** lets you view portal activity; - **Security Administrator** is needed for general diagnostic settings. Review +1. Microsoft Entra ID roles and Azure RBAC are separate. Use the roles in + [portal access](#1-choose-your-tenant-and-access) and [export setup](#3-export-logs-for-ongoing-monitoring). + Review [workspace/table access](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/manage-access) separately for queries. An external-tenant delegation does not grant every alert-management or role-assignment operation in the subscription-owning workforce tenant. @@ -322,22 +322,19 @@ and [triage, retention, and costs](#7-triage-privacy-retention-and-costs). ### Inspect schema and arrival delays -1. Check your workspace [query scope](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-tutorial#open-log-analytics), - not the tutorial's demo environment or Application Insights `requests`/`traces` context. - Use [Simple mode's table selector](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-simple-mode#get-started-in-simple-mode) - if you prefer a point-and-click preview. -2. Expand a row using its [chevron](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-tutorial#expand-and-sort-records). - `AADTenantId` is the source directory; standard `TenantId` is the **workspace ID**. - `AppId` is the app client ID, not an Object ID or provider API ID. -3. In **SigninLogs**, `ResultType` is a **string**: zero indicates success; populated - nonzero values are authentication codes. Keep empty values as unknown. - `CreatedDateTime` is sign-in initiation in UTC; `TimeGenerated` is the workspace log - timestamp. Use the ingestion-time guidance below rather than assuming their difference - measures collection delay. -4. Adapt the [ingestion-latency reference](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/data-ingestion-time) +1. Check the [workspace scope](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-tutorial#open-log-analytics), + not a demo workspace or Application Insights `requests`/`traces` context. + Preview with [Simple mode](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-simple-mode#get-started-in-simple-mode) + and [expand a row](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-tutorial#expand-and-sort-records). +2. Verify IDs and result types with the [field checks below](#field-checks-for-the-recommended-monitors). + In `SigninLogs`, `ResultType` is a **string**: zero is success, populated nonzero values are + authentication codes, and empty values remain unknown. +3. `CreatedDateTime` is sign-in initiation in UTC; `TimeGenerated` is the workspace timestamp, + not an ingestion-delay measurement. Adapt the + [ingestion-latency reference](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/data-ingestion-time) and [standard-column timing guidance](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-standard-columns#_timereceived-column) to your `SigninLogs` scope to measure delay, including missing timestamps. - These generic examples are not a ready-made Entra monitor or delivery-latency measure. + These are not ready-made identity monitors or delivery-latency measures. ### Field checks for the recommended monitors diff --git a/dotnet/README.md b/dotnet/README.md index 5f647df..7004c97 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -1,51 +1,36 @@ # External Phone Provider Function: C# (.NET isolated worker) +[Documentation](../docs/README.md) | [Settings](#environment-configuration) | [Request behavior](#request-behavior) | [Source](#source) + Implements the shared [contract](../docs/CONTRACT.md) with one `SendOtp` request pipeline and one selected provider per deployment. Target: .NET 8 isolated worker, Azure Functions v4. ## Setup and deployment -**Customer deployment:** start with the [root guide](../README.md). Guided setup builds with the -.NET 8 SDK, publishes this runtime, and configures its Azure settings. Continue with -[provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication); -do not start a local host or republish as another onboarding step. - -**The steps below are optional developer/manual deployment work.** Guided providers are -Telesign and Soprano; other adapters require separate integration. - -For multiple regional origins behind one URL, see [manual Front Door onboarding](../docs/FRONTDOOR.md). -No Front Door deployment script or readiness handler is supplied. The reported multi-region trials -used JavaScript; validate an equivalent .NET readiness implementation and deployment separately. - -1. Follow [customer onboarding](../docs/ONBOARDING.md). Set `EPP_PROVIDER_NAME` to the selected - adapter's `Name` (`` is only a placeholder). -2. Consult the selected adapter in [Src/Providers/](Src/Providers/) for required credentials and - options. For API-key authentication, store credentials in Key Vault under the provider's secret names - and grant the Function's managed identity *Key Vault Secrets User*. For OAuth, complete the - [provider-administrator handoff](../docs/ONBOARDING.md#soprano-provider-administrator-handoff) - instead of creating API-key secrets. Configure the matching endpoints/options for either mode. -3. Base private local settings on [../docs/local.settings.sample.json](../docs/local.settings.sample.json), - replacing placeholders and selecting `FUNCTIONS_WORKER_RUNTIME=dotnet-isolated`. Put settings - at the app root beside [host.json](host.json). Configure decryption from the - [shared catalog](../docs/CONTRACT.md#4-configuration-app-settings--env) and caller trust through - [Easy Auth](../docs/ONBOARDING.md#2-provision-encryption-and-deployment-trust), the only authentication - gate before the anonymous Function. Enable `requireAuthentication=true`, - `unauthenticatedClientAction=Return401` and `requireHttps=true`; pin the trusted tenant issuer, - endpoint-app `allowedAudiences` and a nonempty `allowedApplications` list for the authorized Microsoft - caller. Do not exclude SendOtp. There is no backup application token validation; never expose the - endpoint to the public internet with Easy Auth disabled or bypassed. - - `SendOtp` uses Azure Functions `[FromBody]` binding to create `EntraSendOtpPayload`. Malformed JSON, - invalid enum tokens and other deserialization failures are rejected by the Functions binding/runtime - before `SendOtp` runs. Those failures therefore do not produce application `ILogger` events or the - handler's custom error response body; platform diagnostics and responses apply instead. -4. Build [dotnet.csproj](dotnet.csproj), run the offline xUnit suites in - [tests/Epp.Otp.Tests.csproj](tests/Epp.Otp.Tests.csproj), and start the local Functions host from this folder. - Core Tools has no Easy Auth: bind only to loopback, with no tunnels or public forwarding. -5. Publish this app folder to a compatible .NET isolated Function App. Inspect the package: both - [.funcignore](.funcignore) and the project protect local settings; private keys must not be included. - Offline tests cover application behavior, not platform authentication; run the separate - [deployed security checks](../docs/ONBOARDING.md#4-package-deploy-and-verify). +**Deploying the sample?** Use the [five-step guide](../docs/GETTING-STARTED.md). +Guided setup builds with the .NET 8 SDK and publishes. Continue with +[provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication), +not a local host or second deployment. +[Guided provider support](../docs/ONBOARDING.md#provider-credential-names) is narrower than the adapter set. + +**Developing a custom package?** Follow these steps from `dotnet/`: + +1. Create the [local settings below](#environment-configuration). For live integration, choose an + adapter's fixed `Name` from [Providers/](Src/Providers/), configure its endpoints/options, and + complete [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). + No provider is selected by default. +2. Build [dotnet.csproj](dotnet.csproj), run [xUnit tests](tests/Epp.Otp.Tests.csproj), and start + Core Tools here, **on loopback only, without tunnels**; it has no Easy Auth. +3. For manual deployment, publish this app to a compatible .NET isolated Function App. + Inspect the output and [.funcignore](.funcignore); the project excludes local settings, and + private keys must not be packaged. Complete the + [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist) before public + access: it is the only caller-authentication gate; never disable it or exempt SendOtp. + Run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint); + offline tests do not verify platform authentication. + +**Optional:** [Front Door](../docs/FRONTDOOR.md) has no supplied deployment script/readiness handler. +Trials used JavaScript; independently implement and validate .NET readiness and regional deployment. ## Environment configuration @@ -64,14 +49,11 @@ For local evaluation, start Azurite and replace the test-key placeholder in this } ``` -For live delivery, add `EPP_PROVIDER_NAME`, `EPP_PROVIDER_SMS_ENDPOINT`, -`EPP_PROVIDER_VOICE_ENDPOINT`, and matching provider authentication settings to `Values`. -Each request's `channel` selects its required endpoint; the URLs may differ. -A missing, empty, or invalid selected endpoint fails closed without fallback. -Add `EPP_PROVIDER_ACCOUNT_NAME` and adapter-specific options only when required. Optional -`EPP_PROVIDER_TIMEOUT_MS` is a string such as `"1500"`. Replace placeholders; store provider credentials -under the adapter's Key Vault secret names, not in local settings. See the -[complete variable table](../TECHNICAL.md#configure-environment-variables). +For live delivery, use the [shared settings table](../TECHNICAL.md#configure-environment-variables) +for `EPP_PROVIDER_NAME`, both channel endpoints, authentication, and adapter-specific options. +Keep values as strings, including `EPP_PROVIDER_TIMEOUT_MS`, for example `"1500"`. +Replace placeholders; credentials belong in the adapter's Key Vault secrets, not this file. +A missing/invalid selected channel endpoint fails closed without fallback. Core Tools loads `Values` into environment variables. [AppConfig.Read](Src/AppConfig.cs) reads them through `IEnv`; direct worker execution and unit tests do not automatically load local settings. @@ -87,28 +69,22 @@ or the offline tests' injected environment and secret resolver for local develop ## Request behavior -`POST /api/SendOtp` uses the same request and trust boundaries as the other runtimes. Incoming -`mode`, `channel`, `ttlSeconds` and `tenantId` are request data, not deployment authentication settings. -Easy Auth authenticates and authorizes the caller before the anonymous handler validates the payload -and decrypts the JWE. Incoming `Authorization` is not parsed or echoed by the handler. JWE does not -authenticate the caller: anyone with the public key can encrypt a request, and a fixed nonce is not authentication. -Requests that bind successfully retain the application validation, PII-safe logging and response lifecycle. - -For any provider, use incoming `mode: 2` or `mode: "evaluation"` to evaluate without sending. -Platform authentication on Azure, handler validation and decryption run, but provider lookup, provider Key Vault -reads and provider HTTP do not. No provider configuration or diagnostic environment flag is required. -Live requests use the configured provider's API key or OAuth token and -await acceptance before returning the nonce; failures omit it. Acceptance is not handset delivery. -Platform/key prerequisites and HTTP outcomes are defined in the -[contract](../docs/CONTRACT.md#evaluation-no-provider-send). - -For Soprano voice, the adapter extracts the first six-digit passcode from the rendered message. It -uses the request's nonblank locale as the language, falling back to `en-US`, and sends fixed gender -`1` and loop `2`. These values require no additional environment settings. Soprano SMS continues to -forward the rendered message unchanged. - -Telesign SMS also forwards the rendered message unchanged. Telesign voice comma-separates each -six-digit numeric run that is not part of a longer number and repeats the complete paced message twice. +`POST /api/SendOtp` follows the [shared request/response contract](../docs/CONTRACT.md#1-http-api). +Easy Auth authenticates; JWE decryption, nonce values, and request fields do not. +The handler never parses or echoes incoming `Authorization`. + +**.NET binding runs first:** `[FromBody]` creates `EntraSendOtpPayload`. Malformed JSON, invalid +enum tokens, or other deserialization errors can fail before `SendOtp`, its `ILogger` events, +or its custom error response. Successfully bound requests retain application validation, +privacy-safe logging, and response behavior. See [binding differences](../docs/CONTRACT.md#runtime-binding-differences). + +Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) for no-send validation: +authentication and decryption still run, but request-path provider lookup, credentials, and HTTP do not. +Live success waits for acceptance, **not handset receipt**; failures omit the nonce. +Never blindly retry a timeout. + +For message/voice mapping, see the [Soprano delivery contract](../docs/CONTRACT.md#encrypteddeliverycontext-jwe) +and [Telesign integration](../TECHNICAL.md#telesign-epp). ## Source @@ -152,7 +128,7 @@ shared-acquisition behavior applies here. Evaluation remains independent. | [Src/EntraSendOtpPayload.cs](Src/EntraSendOtpPayload.cs) | Bound request model, strict channel/mode converters and semantic validation | | [Src/JweDeliveryContext.cs](Src/JweDeliveryContext.cs) | Pinned JWE decryption and decrypted delivery context | | [Src/CredentialTokenService.cs](Src/CredentialTokenService.cs) | Provider-supplied retrieval, one expiring `MemoryCache` value and startup warmup | -| [Src/OtpLog.cs](Src/OtpLog.cs) | Source-generated, strongly typed [structured logging events](../docs/CONTRACT.md#application-logs) | +| [Src/OtpLog.cs](Src/OtpLog.cs) | Source-generated, strongly typed [structured logging events](../docs/LOGGING.md#event-sequence) | | [Src/PhoneProviderBase.cs](Src/PhoneProviderBase.cs) | Provider extension contract and shared typed JSON/HTTP transport | | [Src/Providers/](Src/Providers/) | Provider identity, credential delegation and API-specific request/response protocols | | [Src/SecretResolver.cs](Src/SecretResolver.cs) | Key Vault transport; `ISecretResolver.ResolveAsync` accepts cancellation | diff --git a/javascript/README.md b/javascript/README.md index 460301f..5841814 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -1,47 +1,34 @@ # External Phone Provider Function: JavaScript -A Node.js Azure Function implementing the shared [contract](../docs/CONTRACT.md). The Function owns -an explicit parse → decrypt → evaluation short-circuit or provider selection → credential resolution -→ provider request/transport/result → response flow. One provider is selected per deployment, and -each provider owns its credentials, wire request, response interpretation, outcome and failure class. +[Documentation](../docs/README.md) | [Settings](#environment-configuration) | [Request behavior](#request-behavior) | [Source](#source-and-extension-points) + +A Node.js Azure Function implementing the shared [HTTP contract](../docs/CONTRACT.md). +One configured provider owns its credentials, request mapping, and response interpretation. ## Setup -**Customer deployment:** start with the [root guide](../README.md). Guided setup publishes this -runtime and its Azure settings; continue with [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). -Do not create local settings or republish after a successful guided deployment. - -**The steps below are optional developer/manual deployment work**, not additional onboarding -requirements. Guided providers are Telesign and Soprano; other adapters require separate integration. - -For multiple regional origins behind one URL, see [manual Front Door onboarding](../docs/FRONTDOOR.md). -The JavaScript evaluation trials used a separate readiness handler; it is not included in this -sample's release package. No Front Door deployment script is supplied. - -1. Follow [customer onboarding](../docs/ONBOARDING.md). Choose a bundled provider and set - `EPP_PROVIDER_NAME` to its fixed id; `` is a placeholder, not a default. -2. Consult the selected implementation in [src/functions/providers/](src/functions/providers/) - for required credentials and options. For API-key authentication, store credential values under - the provider's Key Vault secret names and grant the Function's managed identity *Key Vault Secrets User*. - For OAuth, complete the [provider-administrator handoff](../docs/ONBOARDING.md#soprano-provider-administrator-handoff) - instead of creating API-key secrets. Configure the matching endpoints and required options for either mode. - This guide does not duplicate individual API contracts. -3. Use [../docs/local.settings.sample.json](../docs/local.settings.sample.json) as a starting point, - replacing placeholders with the selected provider's settings. Keep local settings private at - the app root beside [host.json](host.json), with `FUNCTIONS_WORKER_RUNTIME=node`. -4. Configure decryption from the [shared catalog](../docs/CONTRACT.md#4-configuration-app-settings--env). - Follow [platform trust setup](../docs/ONBOARDING.md#2-provision-encryption-and-deployment-trust): Easy - Auth is the only caller-authentication gate before the anonymous Function. Enable - `requireAuthentication=true`, `unauthenticatedClientAction=Return401` and `requireHttps=true`; pin the - trusted tenant issuer, endpoint-app `allowedAudiences` and a nonempty `allowedApplications` list for - the authorized Microsoft caller. Do not exclude SendOtp. There is no backup application token validation; - never expose the endpoint to the public internet with Easy Auth disabled or bypassed. -5. Install dependencies from [package.json](package.json), run its offline test script and start the - local Functions host from this folder. Core Tools has no Easy Auth: bind only to loopback, with no - tunnels or public forwarding. Publish this app folder only, preserving dependencies and observing - [.funcignore](.funcignore); inspect the package before upload. Offline tests cover application - behavior, not platform authentication; run the separate - [deployed security checks](../docs/ONBOARDING.md#4-package-deploy-and-verify). +**Deploying the sample?** Use the [five-step guide](../docs/GETTING-STARTED.md). +After guided setup, go to [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication), +not local settings or another deployment. [Guided provider support](../docs/ONBOARDING.md#provider-credential-names) +is narrower than the bundled adapter set. + +**Developing a custom package?** Follow these steps from `javascript/`: + +1. Create the [local settings below](#environment-configuration). For live integration, choose a + fixed provider ID from [providers/](src/functions/providers/), configure its endpoints/options, + and complete [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). + No provider is selected by default. +2. Install [package.json](package.json) dependencies, run its offline test script, and start Core Tools + from this folder. **Bind only to loopback, without tunnels:** Core Tools has no Easy Auth. +3. For manual deployment, [package](../TECHNICAL.md#build-zips-locally) this app only, preserving + dependencies and [.funcignore](.funcignore). Inspect for secrets before upload. Complete the + [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist) before public access: + it is the only caller-authentication gate; never disable it or exempt SendOtp. + Then run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint); + offline tests do not verify platform authentication. + +**Optional:** [Front Door](../docs/FRONTDOOR.md) uses multiple regional origins behind one URL. +The JavaScript trials' readiness handler is not released, and no Front Door deployment script is supplied. ## Environment configuration @@ -61,13 +48,11 @@ the test-key placeholder in this minimal setup: } ``` -For live delivery, add `EPP_PROVIDER_NAME`, `EPP_PROVIDER_SMS_ENDPOINT`, -`EPP_PROVIDER_VOICE_ENDPOINT`, and matching provider authentication settings to `Values`. -Each request's `channel` selects its required endpoint; the URLs may differ. -A missing, empty, or invalid selected endpoint fails closed without fallback. -Add `EPP_PROVIDER_ACCOUNT_NAME` and any provider-specific options only when required. Optional -`EPP_PROVIDER_TIMEOUT_MS` is a string such as `"1500"`. Replace placeholders; do not put API keys in -this file. See the [complete variable table](../TECHNICAL.md#configure-environment-variables). +For live delivery, use the [shared settings table](../TECHNICAL.md#configure-environment-variables) +for `EPP_PROVIDER_NAME`, both channel endpoints, authentication, and adapter-specific options. +Keep `Values` as strings, including `EPP_PROVIDER_TIMEOUT_MS`, for example `"1500"`. +Replace placeholders; API keys belong in Key Vault, not this file. +A missing/invalid selected channel endpoint fails closed without fallback. Core Tools copies `Values` into the process environment; direct Node processes and the offline tests do **not** automatically load this file. [AppConfig](src/functions/config.js) reads `process.env` @@ -75,19 +60,6 @@ once per call to `readConfig()`. Restart the host after changing settings. Confi storage other than Azurite separately; do not copy a local emulator connection into Azure. Core Tools does not resolve Key Vault references locally; supply the local test PEM or base64 PEM directly. -Use `EPP_PROVIDER_NAME`, the channel-specific endpoint settings above, and `EPP_PROVIDER_TIMEOUT_MS`; -configure caller authentication in Easy Auth, not application settings. Add only provider options -read by the selected adapter, such as a service-plan ID. - -The Soprano provider uses the configured complete endpoint and an OAuth bearer token. For voice, it -extracts the first six-digit passcode from the rendered message and sends fixed synthesis values: -gender `1` and loop `2`. It uses the request's nonblank locale as the language, falling back to -`en-US` when the locale is absent or invalid. These values require no additional environment -settings. Soprano SMS continues to forward the rendered message unchanged. The Telesign provider -uses the configured complete endpoint and API-key credentials from Key Vault. Telesign SMS forwards -the rendered message unchanged; Telesign voice comma-separates each six-digit numeric run that is -not part of a longer number and repeats the complete paced message twice. - For Azure, set these application variables on the Function App/slot's **Environment variables → App settings** page and use a Key Vault reference for the private PEM. The provider-secret resolver uses managed identity; signing into the CLI locally does not supply that identity. Local evaluation avoids @@ -95,21 +67,18 @@ provider lookup, while tests inject mocked credentials and HTTP. Keep the local ## Request behavior -Easy Auth authenticates and authorizes the caller before `POST /api/SendOtp`; the anonymous handler -validates the envelope and decrypts the JWE, without parsing or echoing incoming `Authorization`. -JWE does not authenticate the caller: anyone with the public key can encrypt a request, and a fixed nonce -is not authentication. Request `mode`, `channel`, `ttlSeconds` and `tenantId` are request data, not -environment settings or sources of identity trust. SMS forwards the caller-rendered message; -voice uses the provider-specific extraction/pacing described above. +`POST /api/SendOtp` follows the [shared request/response contract](../docs/CONTRACT.md#1-http-api). +Easy Auth authenticates; JWE decryption, nonce values, and request fields do not. +The handler never parses or echoes incoming `Authorization`. -For non-delivery validation, use incoming `mode: 2` or `mode: "evaluation"`. This evaluation path -works for every provider without provider configuration, provider Key Vault reads or provider HTTP; -platform authentication on Azure and handler decryption still run. No diagnostic environment flag is needed. See the -[evaluation contract](../docs/CONTRACT.md#evaluation-no-provider-send) for authentication/key prerequisites. +Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) for no-send validation: +authentication and decryption still run, but request-path provider lookup, credentials, and HTTP do not. +Live success waits for acceptance, **not handset receipt**; failures omit the nonce. +Never blindly retry a timeout. -Live requests use the configured provider's API key or OAuth token and await acceptance before returning the nonce. -Acceptance is not handset delivery; failures omit the nonce, and timeouts must not trigger blind -retries. The shared contract defines validation, HTTP outcomes and privacy-safe logging. +For message/voice mapping and credential rules, see the +[Soprano delivery contract](../docs/CONTRACT.md#encrypteddeliverycontext-jwe) and +[Telesign integration](../TECHNICAL.md#telesign-epp). ## Source and extension points @@ -121,9 +90,9 @@ For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHT [JavaScript service-event queries](../docs/MONITORING.md#4-javascript-service-event-queries). Logs are JSON `service` events ending in `request_completed`, not a `logType: "request"` summary. -`EPP_KEY_VAULT_CACHE_ENABLED` controls API-key caching; `EPP_ACCESS_TOKEN_CACHE_ENABLED` controls -OAuth caching. Both default to `true`. Setting the selected switch to `false` acquires credentials -per live request, with no startup preparation, polling, or cross-request reuse. +The [cache switches and defaults](../docs/CONTRACT.md#credential-caching-and-refresh) apply here. +Disabling the selected cache removes startup preparation, polling, and cross-request reuse, +not live credential acquisition. With caching enabled, the app-start hook selects `ApiKeyCache` or `AccessTokenCache` from the provider credential spec. Only that cache starts: API keys use Key Vault and `lru-cache`; access tokens use the MI/Entra SDKs, diff --git a/python/README.md b/python/README.md index 8a2e810..510c1e3 100644 --- a/python/README.md +++ b/python/README.md @@ -1,46 +1,34 @@ # External Phone Provider Function: Python (v2 model) +[Documentation](../docs/README.md) | [Settings](#environment-configuration) | [Request behavior](#request-behavior) | [Source](#source) + Implements the shared [contract](../docs/CONTRACT.md) with a direct Azure Function flow and one selected provider per deployment. Target: Python 3.11, Azure Functions v4, Python v2 programming model. ## Setup and deployment -**Customer deployment:** start with the [root guide](../README.md). Guided setup performs the -Azure remote build, publishes this runtime, and configures its Azure settings. Continue with -[provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication); -do not install Python locally or republish as another onboarding step. - -**The steps below are optional developer/manual deployment work.** Guided providers are -Telesign and Soprano; other adapters require separate integration. - -For multiple regional origins behind one URL, see [manual Front Door onboarding](../docs/FRONTDOOR.md). -No Front Door deployment script or readiness handler is supplied. The reported multi-region trials -used JavaScript; validate an equivalent Python readiness implementation and deployment separately. - -1. Follow [customer onboarding](../docs/ONBOARDING.md). Set `EPP_PROVIDER_NAME` to the selected - provider id (`` is only a placeholder). -2. Consult the selected provider in [src/providers/](src/providers/) for required credentials and - options. For API-key authentication, store credentials in Key Vault under the provider-owned secret names - and grant the Function's managed identity *Key Vault Secrets User*. For OAuth, complete the - [provider-administrator handoff](../docs/ONBOARDING.md#soprano-provider-administrator-handoff) - instead of creating API-key secrets. Configure the matching endpoints/options for either mode. -3. Base private local settings on [../docs/local.settings.sample.json](../docs/local.settings.sample.json), - replacing placeholders and selecting `FUNCTIONS_WORKER_RUNTIME=python`. Put settings at the - app root beside [host.json](host.json). Configure decryption from the - [shared catalog](../docs/CONTRACT.md#4-configuration-app-settings--env) and caller trust through - [Easy Auth](../docs/ONBOARDING.md#2-provision-encryption-and-deployment-trust), the only authentication - gate before the anonymous Function. Enable `requireAuthentication=true`, - `unauthenticatedClientAction=Return401` and `requireHttps=true`; pin the trusted tenant issuer, - endpoint-app `allowedAudiences` and a nonempty `allowedApplications` list for the authorized Microsoft - caller. Do not exclude SendOtp. There is no backup application token validation; never expose the - endpoint to the public internet with Easy Auth disabled or bypassed. -4. Use a virtual environment, install [requirements.txt](requirements.txt) and pytest, then run the - offline [tests/](tests/) from this folder. Start the local Functions host from this app root. - Core Tools has no Easy Auth: bind only to loopback, with no tunnels or public forwarding. -5. Publish this folder to a compatible Linux Python Function App with dependencies or a supported - remote build. Inspect the package and apply [.funcignore](.funcignore); keep local settings and keys private. - Offline tests cover application behavior, not platform authentication; run the separate - [deployed security checks](../docs/ONBOARDING.md#4-package-deploy-and-verify). +**Deploying the sample?** Use the [five-step guide](../docs/GETTING-STARTED.md). +Guided setup builds remotely and publishes; no local Python installation or second deployment. +Continue with [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). +[Guided provider support](../docs/ONBOARDING.md#provider-credential-names) is narrower than the adapter set. + +**Developing a custom package?** Follow these steps from `python/`: + +1. Create the [local settings below](#environment-configuration). For live integration, choose a + fixed provider ID from [providers/](src/providers/), configure its endpoints/options, and + complete [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). + No provider is selected by default. +2. Use a virtual environment, install [requirements.txt](requirements.txt) and pytest, and run + [tests/](tests/). Start Core Tools here, **on loopback only, without tunnels**; it has no Easy Auth. +3. For manual deployment, publish this app to a compatible Linux Python Function with dependencies + or a supported remote build. Inspect [.funcignore](.funcignore) and the package for private + settings/keys. Complete the [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist) + before public access: it is the only caller-authentication gate; never disable it or exempt SendOtp. + Run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint); + offline tests do not verify platform authentication. + +**Optional:** [Front Door](../docs/FRONTDOOR.md) has no supplied deployment script/readiness handler. +Trials used JavaScript; independently implement and validate Python readiness and regional deployment. ## Environment configuration @@ -59,14 +47,11 @@ For local evaluation, start Azurite and replace the test-key placeholder in this } ``` -For live delivery, add `EPP_PROVIDER_NAME`, `EPP_PROVIDER_SMS_ENDPOINT`, -`EPP_PROVIDER_VOICE_ENDPOINT`, and matching provider authentication settings to `Values`. -Each request's `channel` selects its required endpoint; the URLs may differ. -A missing, empty, or invalid selected endpoint fails closed without fallback. -Add `EPP_PROVIDER_ACCOUNT_NAME` and any adapter-specific options only when required. Keep values as -strings, including optional `EPP_PROVIDER_TIMEOUT_MS: "1500"`. Replace placeholders; provider API -keys belong in the provider-named Key Vault secrets, not this file. See the -[complete variable table](../TECHNICAL.md#configure-environment-variables). +For live delivery, use the [shared settings table](../TECHNICAL.md#configure-environment-variables) +for `EPP_PROVIDER_NAME`, both channel endpoints, authentication, and adapter-specific options. +Keep values as strings, including `EPP_PROVIDER_TIMEOUT_MS: "1500"`. Replace placeholders; +API keys belong in provider-named Key Vault secrets, not this file. +A missing/invalid selected channel endpoint fails closed without fallback. Core Tools loads `Values` into `os.environ`. Direct Python execution and pytest do not automatically read local settings. [read_config](src/config.py) returns an `AppConfig` object; the handler/engine @@ -82,27 +67,17 @@ tests on an ordinary workstation, and bind local hosts only to loopback. ## Request behavior -`POST /api/SendOtp` uses the same request and trust boundaries as the other runtimes. Incoming -`mode`, `channel`, `ttlSeconds` and `tenantId` are request data, not deployment authentication settings. -Easy Auth authenticates and authorizes the caller before the anonymous handler validates the envelope -and decrypts the JWE. Incoming `Authorization` is not parsed or echoed by the handler. JWE does not -authenticate the caller: anyone with the public key can encrypt a request, and a fixed nonce is not authentication. - -For any provider, use incoming `mode: 2` or `mode: "evaluation"` to evaluate without sending. -Platform authentication on Azure, handler validation and decryption run, but provider lookup, provider Key Vault -reads and provider HTTP do not. No provider configuration or diagnostic environment flag is required. -Live requests use the configured provider's API key or OAuth token and -await acceptance before returning the nonce; failures omit it. Acceptance is not handset delivery. -Platform/key prerequisites and HTTP outcomes are defined in the -[contract](../docs/CONTRACT.md#evaluation-no-provider-send). +`POST /api/SendOtp` follows the [shared request/response contract](../docs/CONTRACT.md#1-http-api). +Easy Auth authenticates; JWE decryption, nonce values, and request fields do not. +The handler never parses or echoes incoming `Authorization`. -For Soprano voice, the adapter extracts the first six-digit passcode from the rendered message. It -uses the request's nonblank locale as the language, falling back to `en-US`, and sends fixed gender -`1` and loop `2`. These values require no additional environment settings. Soprano SMS continues to -forward the rendered message unchanged. +Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) for no-send validation: +authentication and decryption still run, but request-path provider lookup, credentials, and HTTP do not. +Live success waits for acceptance, **not handset receipt**; failures omit the nonce. +Never blindly retry a timeout. -Telesign SMS also forwards the rendered message unchanged. Telesign voice comma-separates each -six-digit numeric run that is not part of a longer number and repeats the complete paced message twice. +For message/voice mapping, see the [Soprano delivery contract](../docs/CONTRACT.md#encrypteddeliverycontext-jwe) +and [Telesign integration](../TECHNICAL.md#telesign-epp). ## Source @@ -115,9 +90,9 @@ For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHT Python emits fixed messages with `event_name` and other logging extras, not a universal JSON request summary. Verify which properties the host/export pipeline preserves. -`EPP_KEY_VAULT_CACHE_ENABLED` controls API-key caching; `EPP_ACCESS_TOKEN_CACHE_ENABLED` controls -OAuth caching. Both default to `true`. Setting the selected switch to `false` acquires credentials -per live request, with no startup preparation, polling, or cross-request reuse. +The [cache switches and defaults](../docs/CONTRACT.md#credential-caching-and-refresh) apply here. +Disabling the selected cache removes startup preparation, polling, and cross-request reuse, +not live credential acquisition. With caching enabled, worker initialization selects `ApiKeyCache` or `AccessTokenCache` from the provider's credential specification. Only the selected cache starts: API keys use Key Vault and `cachetools.TTLCache`; access tokens use diff --git a/setup/docs/README.md b/setup/docs/README.md index e8bb6ce..c64b948 100644 --- a/setup/docs/README.md +++ b/setup/docs/README.md @@ -1,16 +1,19 @@ # EPP endpoint setup reference -New to the sample? Follow the [five-step setup guide](../../docs/GETTING-STARTED.md). This reference explains the -permissions, hosting choices, script prompts, and maintenance steps in more detail. +[Documentation](../../docs/README.md) | [First deployment](../../docs/GETTING-STARTED.md) | [Troubleshooting](Troubleshooting.md) -The three stages in this reference are: +Use this reference for setup choices and maintenance. For a first deployment, follow the +five-step guide above; no repository clone or manual Bicep download is needed. -1. [Register a dedicated application](#step-1---manually-create-the-application). -2. [Run the setup script](#step-2---download-and-run-one-script), after checking - [prerequisites](#prerequisites-for-step-2). It downloads its support files and deploys the - resources and code; no repository clone or manual Bicep download is needed. -3. [Validate and activate policy](#step-3---manually-validate-and-activate-policy), with your - onboarding owner and policy administrator. The script does not activate policy. +| Task | Section | +| --- | --- | +| Prepare access, tools, and hosting | [Prerequisites](#prerequisites-for-step-2), [permissions](#permissions-are-separate), [plans](#service-plan-selection) | +| Register the dedicated app | [Step 1](#step-1---manually-create-the-application) | +| Run or review setup | [Step 2](#step-2---download-and-run-one-script), [certificate choice](#encryption-certificate-selection), [cancel at approval](#reviewing-the-plan-without-deploying) | +| Use known inputs and versions | [Noninteractive example](#noninteractive-example), [source versioning](#source-versioning) | +| Find deployment results | [Summary fields](#read-the-deployment-summary) | +| Validate, activate, and maintain | [Step 3](#step-3---manually-validate-and-activate-policy), [renewal](#encryption-certificate-lifecycle), [rollback](#rollback-and-decommissioning) | +| Develop setup changes | [Offline checks](#offline-setup-checks) | Confirm [provider access and the approved Microsoft procedure](../../docs/ONBOARDING.md#before-purchasing-or-deploying) before purchasing or deploying. A provider purchase alone does not enable the tenant feature or authorize its API. @@ -21,23 +24,15 @@ not coordinate encryption keys or Front Door origins. ## Availability -Choose a **Global or EU provider route** (the prompt calls it **Tenant scope**), -**Telesign or Soprano**, and an -Azure Function **platform**: Node.js, .NET, or Python, plus a **service plan**: Flex Consumption FC1 -or Premium EP1. By default, setup resolves the source repository's latest stable -`epp-packages-*` release produced by CI. A public fork's test branch can use its matching fork release. -There is no package URL or checksum to enter. Setup verifies `SHA256SUMS.txt` automatically and -performs the required build and publication for the selected language. -The Function endpoint accepts both SMS and voice; the request body's `channel` selects its provider URL. -Setup does not prompt for a channel or write a channel restriction to app settings. - -Provider profiles contain complete region route objects at `deployment.routes.global` and -`deployment.routes.eu`. Each route has separate `smsEndpoint` and `voiceEndpoint` URLs plus shared -authentication and timing settings. The URLs can differ; setup always provisions both as -`EPP_PROVIDER_SMS_ENDPOINT` and `EPP_PROVIDER_VOICE_ENDPOINT`. Telesign contains its supplied -route URLs, tenant, authentication, and timings. Soprano contains its provider tenant, Global/EU -routes, API application ID, scope, authentication, and timings. The provider files contain the -complete deployment contract. +Choose **Telesign or Soprano**, **Global/EU** provider routing (prompt: **Tenant scope**), +**Node.js/.NET/Python**, and **FC1/EP1** hosting. Setup verifies `SHA256SUMS.txt`, builds as needed, +and publishes; no package URL/hash input is needed. See [source/package selection](#source-versioning). + +Both SMS and voice are configured. Request `channel` selects the URL; there is no setup channel +prompt or app-setting restriction. Profile objects `deployment.routes.global` and +`deployment.routes.eu` contain `smsEndpoint`, `voiceEndpoint`, tenant, authentication, and timing; +Soprano also supplies its API app ID/scope. Setup writes the URLs to +`EPP_PROVIDER_SMS_ENDPOINT` and `EPP_PROVIDER_VOICE_ENDPOINT`; they can differ. Use a Function package that supports these channel-specific settings. Both URLs are configured explicitly; neither channel uses a shared endpoint fallback. Older packages must be upgraded. @@ -105,15 +100,15 @@ Use a dedicated nonproduction tenant/subscription for the first deployment. [provider authentication handoff](../../docs/ONBOARDING.md#complete-provider-authentication) after setup creates the vault and identity identifiers needed for those steps. -After the single Step 2 approval, PowerShell makes the dedicated app organizational multi-tenant, -restricts it through the Entra allowed-tenants preview to its home tenant plus the selected provider -tenant from the provider JSON, preserving any existing allowed provider tenants, -adds the `Epp.Invoke` application permission, creates/reuses its enterprise application, requires -assignment, creates/reuses the Microsoft phone-provider service principal, and assigns `Epp.Invoke`. -It also grants that Microsoft service principal tenant-wide Microsoft Graph `Application.Read.All`, -adds the hostname-based identifier URI and public JWE encryption certificate, and configures Easy -Auth to allow only the Microsoft phone-provider application. Soprano additionally creates the -disclosed outbound managed-identity federated credential. +After Step 2 approval, setup configures: + +- Organizational multitenancy and the Microsoft Entra ID allowed-tenants preview: home tenant, + selected provider tenant, and preserved existing allowed provider tenants. +- The app's enterprise application with assignment required, `Epp.Invoke`, and its assignment to + the created/reused Microsoft phone-provider service principal. +- That Microsoft principal's tenant-wide Graph `Application.Read.All`, the hostname-based + identifier URI, public JWE certificate, and Easy Auth restricted to the Microsoft caller. +- The disclosed outbound managed-identity federated credential for Soprano. ## Prerequisites for Step 2 @@ -173,7 +168,7 @@ automatically grant them to every member of your operations team. ### Install and check workstation tools -Use the official [PowerShell installation](https://learn.microsoft.com/powershell/scripting/install/installing-powershell), +Install missing tools using [PowerShell installation](https://learn.microsoft.com/powershell/scripting/install/installing-powershell), [Azure CLI installation](https://learn.microsoft.com/cli/azure/install-azure-cli), and [.NET 8 SDK download](https://dotnet.microsoft.com/download/dotnet/8.0) instructions (SDK only for C#). Open a new **PowerShell 7** window after installation, then check: @@ -194,21 +189,15 @@ These version checks do not establish Azure permissions, quota, feature availabi | .NET | .NET 8 isolated, Functions v4 | Verify source ZIP, publish for Linux with .NET 8, repackage and publish | | Python | Python 3.11, Functions v4 | Verify source ZIP; One Deploy with remote build on FC1, or validate/download and publish the SCM-built output on EP1 | -Package hashes are still checked; removing the **customer prompt** does not disable integrity -verification. Source and deployed-package hashes are recorded separately when a build changes the bytes. -For FC1 Python, One Deploy builds and stores the output inside Azure; the summary records the -verified source hash and leaves `packageSha256` null rather than reporting it as the built-output hash. +Setup verifies source hashes and records built-package hashes separately when bytes change. +FC1 Python builds inside Azure: its summary keeps the verified source hash and leaves +`packageSha256` null, not a guessed built hash. -Setup normally detects these automatically. For noninteractive execution, preinstall prerequisites -or explicitly allow Bicep installation with `-InstallPrerequisites`; see the -[complete example](#noninteractive-example). - -Install Azure CLI through its official installation instructions if necessary. Setup starts by -reading cached enabled user subscriptions without changing the CLI default. You can select a -tenant and subscription, refresh the cache, or explicitly start device-code sign-in. Microsoft -Graph uses the resulting CLI directory through `az ad` and `az rest`; there is no separate Graph -PowerShell sign-in. Authentication, Bicep installation, MFA, and administrator-consent prompts -are not resource-creation approvals. +For [unattended execution](#noninteractive-example), preinstall prerequisites or explicitly allow +Bicep installation with `-InstallPrerequisites`. Azure CLI must already be installed. +The account picker reads enabled user subscriptions without changing the CLI default; select, +refresh, or use device-code sign-in. Graph reuses that directory through `az ad`/`az rest`. +Sign-in, MFA, consent, and Bicep-installation prompts are **not deployment approval**. Use `-ForceAuthentication` when the machine has ambiguous cached identities. It requires interactive Azure CLI device-code authentication, does not clear shared token caches, and cannot be combined @@ -252,14 +241,10 @@ The flow is: When `-Provider` is omitted, setup reads the application's allowed-tenants configuration: no supported provider prompts for a name, one asks for confirmation, and several require a name. Existing allowed provider tenants are preserved; provider options are never numbered. - Setup downloads the provider JSON, - resolves one complete regional route containing both SMS and voice endpoints, authentication, app-ID/scope - when applicable, timeout, and retry interval. These values come from the selected profile; - the launcher has no endpoint or credential override parameters. It also resolves the latest stable CI package - release and looks up the language asset name in `packages/catalog.json`; there are no - `PackageUrl` or `PackageSha256` inputs. Use - `-PackageReleaseTag epp-packages--` to pin a previous CI release. Malformed or - disabled profiles still fail before resource creation. + The selected [profile](#availability) supplies both channel URLs and auth/timing values. + The launcher has no endpoint/credential overrides or `PackageUrl`/`PackageSha256` inputs. + It uses `packages/catalog.json` and the latest stable CI release, or your + `-PackageReleaseTag epp-packages--`. Malformed/disabled profiles fail before resource creation. 5. **Service plan selection:** choose **Flex Consumption FC1** (free usage grant, zero always-ready instances, both cache settings `"false"`) or **Premium EP1** (warm instance, both cache settings `"true"`). Supply `-ServicePlan FC1` or `-ServicePlan EP1` to reuse a known selection. @@ -357,18 +342,13 @@ A preflight error is not a successful preview. ### After approving the plan -After approval, setup rechecks the selected subscription and registers only missing -`Microsoft.Web`, `Microsoft.Storage`, `Microsoft.KeyVault`, `Microsoft.OperationalInsights`, -`Microsoft.Insights`, and `Microsoft.ManagedIdentity` providers. Already registered providers are -left alone; existing registrations in progress are reused. Registration and regional checks happen -before certificate creation or Bicep deployment. The read-only preflight does not register anything. - -Azure registers providers region by region. Setup does not unnecessarily wait for a global -`Registered` state when a provider is already `Registering` and exposes the requested region. -Registration metadata is polled with a bounded limit, and recognized regional registration -propagation errors are retried during capability checks/deployment. Permission failures and -unsupported regions remain explicit errors. Registration is subscription-wide and isn't undone -automatically if a later deployment step fails. +Setup rechecks the subscription and registers only missing `Microsoft.Web`, `Microsoft.Storage`, +`Microsoft.KeyVault`, `Microsoft.OperationalInsights`, `Microsoft.Insights`, and +`Microsoft.ManagedIdentity` providers before certificates or deployment. Preflight registers nothing. +Existing registrations are reused; `Registering` is sufficient once the requested region is exposed. +Polling/recognized propagation retries are bounded; permission and unsupported-region errors stop setup. +Subscription-wide registrations are not undone after later failures. See +[registration recovery](Troubleshooting.md#a-required-azure-resource-provider-is-not-registered). Supply known values to shorten the prompts. Replace `` with your selected guided provider's ID (`soprano` or `telesign`); neither is a default or recommendation: @@ -401,13 +381,13 @@ Telemetry uses the system identity; the outbound identity is selected explicitly a global `AZURE_CLIENT_ID`. The Function starts with public ingress disabled. After Bicep creates the vault and permissions, -setup applies the approved certificate action. New certificates are self-signed, exportable -RSA-2048 with subject and Entra display name **`CN=ExternalPhoneProvider`**. Initial creation uses -`phone-provider-encryption`; replacement uses a separate suffixed staging name. Reuse selects the -approved existing certificate version and preserves its registered key ID and display name. -Setup pins `EPP_DECRYPTION_KEY_PEM` to the **versioned PEM backing secret**, verifies resolution, -then writes exactly one `AsymmetricX509Cert` / `Encrypt` entry while preserving other credentials. -It **reads back and verifies Easy Auth before enabling ingress**. +setup applies the [approved certificate action](#encryption-certificate-selection). +New certificates are self-signed, exportable RSA-2048 with subject/display name +**`CN=ExternalPhoneProvider`**. Initial creation uses `phone-provider-encryption`; replacement uses +a suffixed staging name; reuse preserves the selected key ID/display name. +Setup pins `EPP_DECRYPTION_KEY_PEM` to its **versioned PEM backing secret**, verifies resolution, +and registers one encryption entry without changing other credentials. +It **verifies Easy Auth before enabling ingress**. SCM basic authentication stays disabled for both plans. FC1 uses Entra-authenticated **One Deploy** for every language, with remote build for Python. Its `functionAppConfig` defines runtime, scaling, and managed-identity deployment storage; setup does not write Premium-only runtime/build or @@ -418,38 +398,27 @@ It never mounts the unbuilt Python source ZIP. EP1 then restarts and synchronize Both plans verify that `SendOtp` is registered before reporting success. On publication/startup failure it disables public ingress again; failure to close ingress is reported explicitly rather than hidden. -Setup refreshes and checks the exact decryption-key reference through App Service with bounded -retries for identity/role propagation. Failure to resolve it stops before changing registered -encryption credentials; infrastructure and app settings may already have changed. This metadata -check does not replace the required authorized evaluation of decryption before policy activation. - -The public certificate and a timestamped identifier -summary are saved to `epp-output` beside the downloaded script, or to `-OutputDirectory`. -The summary includes certificate/secret version identifiers, thumbprint, expiry, and manual renewal -mode. Its service-plan and cache fields record configured settings, not verified runtime cache behavior. -Setup never downloads, writes, or imports the private key locally: only the Function receives -it through its managed-identity Key Vault reference. Certificate creation automatically supplies the -backing secret; setup no longer writes a separate `phone-provider-decryption-key` secret. - -For unattended runs, supply every input including `-ServicePlan FC1` or `-ServicePlan EP1`, -authenticate Azure CLI for both ARM and Microsoft Graph first, and explicitly authorize -the whole displayed plan with **both** `-NonInteractive -ApproveDeployment`. `-NonInteractive` -alone never approves changes. There is no `-Stage`, `-Resume`, `-ConfigPath`, or policy-approval switch. -Existing encryption credentials also require explicit `-CertificateAction Reuse` or `Replace`. -Unattended reuse requires both `-EncryptionKeyId` and `-KeyVaultCertificateId`, even if only one -credential exists. `-ApproveDeployment` alone never selects a certificate or authorizes an -implicit replacement. +Key-reference checks use bounded identity/role propagation retries. Failure stops certificate +registration, but infrastructure/settings may have changed. Metadata resolution does not replace +authorized decryption evaluation before activation. + +Find the public certificate and timestamped [summary](#read-the-deployment-summary) in `epp-output` +beside the script, or `-OutputDirectory`. Plan/cache fields record configuration, not verified +runtime behavior. Setup never downloads, writes, or imports the private key locally: the Function +receives it through its managed-identity reference. The certificate supplies its backing secret; +there is no separate `phone-provider-decryption-key` secret. ### Noninteractive example -This JavaScript deployment template supports both SMS and voice; it is not a credential script. -Replace the three application/tenant/subscription IDs and `` (`soprano` or `telesign`), -and choose the approved provider route, Azure region, prefix, and plan before running. -The preliminary user sign-ins can require MFA and consent; `-NonInteractive` does not create a -headless service-principal deployment path. Install Azure CLI Bicep beforehand using interactive -setup's prerequisite prompt or your approved installation process. -The `az login` step below is for local PowerShell. In Cloud Shell, start in the intended directory -and reuse its existing login instead; token checks use the selected subscription, not `--tenant`. +Supply every input, including `-ServicePlan`, and **both `-NonInteractive -ApproveDeployment`** +to authorize the complete reviewed plan. `-NonInteractive` alone approves nothing; there is no +`-Stage`, `-Resume`, `-ConfigPath`, or policy-approval switch. + +Replace the IDs/provider placeholder and choose approved route, region, prefix, and plan. +This JavaScript example supports both channels. Establish the CLI user session for ARM and Graph +first; MFA/consent may still be required. There is no headless service-principal path. +Preinstall Bicep or explicitly allow its installation. In Cloud Shell, reuse the intended +directory's login instead of the local `az login` below; token checks select the subscription. ```powershell $tenantId = '' @@ -487,8 +456,9 @@ mutation set, not just package upload. Do not use it as a dry run. Add `-PassThru` when automation also needs the structured deployment result. The timestamped summary JSON is saved regardless. -The example above assumes the app has **no encryption credentials**. For a rerun, add -`@reuseCertificate` to the complete setup command using this reviewed parameter set: +The example assumes **no encryption credentials**. Otherwise choose `Reuse` or `Replace`; +approval alone never selects or replaces a certificate. Reuse requires both key ID and exact +certificate version, even with one credential. Add `@reuseCertificate` using this reviewed set: ```powershell $reuseCertificate = @{ @@ -532,10 +502,9 @@ Those fields do not prove credentials, delivery, or monitoring work. Follow ### Encryption certificate lifecycle -The policy for certificates **issued by setup** uses **12-month validity, key reuse, and manual renewal**. It specifies -`EmailContacts` 30 days before expiry, **not `AutoRenew`**. Email is sent only if the customer -separately configures Key Vault certificate contacts; setup does not create contacts or guarantee -notifications. Track the saved expiry and arrange renewal before the certificate expires. +Certificates **issued by setup** use **12-month validity, key reuse, and manual renewal**. +`EmailContacts` runs 30 days before expiry, **not `AutoRenew`**, and needs separately configured +vault contacts. Setup creates no contacts and guarantees no notifications. In **Key Vault > Certificates > Certificate contacts**, add the approved operations/renewal contact and verify the address and ownership. Portal labels can vary; follow @@ -554,17 +523,16 @@ waits for self-signed issuance. Other errors, including pending-operation confli rather than overwriting a disabled, incompatible, or near-expiry staged certificate. Keep more than 30 days of validity remaining. No Key Vault version is deleted as part of application cleanup. -For renewal, coordinate with the EPP owner before expiry. **Replace** stages a new certificate -and switches the single-key endpoint under the approved maintenance plan. Alternatively, use the -supported administrator procedure to synchronize a renewed certificate in Entra and Key Vault, -then select that exact registered version with **Reuse**. Rerunning setup no longer appends a -renewed certificate alongside older credentials, even when it has the same RSA key. +Coordinate renewal with the endpoint owner before expiry. **Replace** stages a new certificate +and switches the single-key endpoint under approved maintenance. Alternatively, synchronize the +renewed certificate in Microsoft Entra ID and Key Vault through the supported administrator +procedure, then **Reuse** that exact registered version. Setup does not append a second encryption +credential, even for the same RSA key. -Key Vault renewal alone does **not** update Entra or the Function's pinned reference. -Reuse does not change an external certificate's issuance/renewal policy. Even if that vault -automatically issues a new version, endpoint synchronization remains manual; this is what the -summary's `certificateRenewal: manual` records. The Function still decrypts in-process with a -single private key, so any rotation must account for other endpoints and cached/in-flight requests. +**Vault renewal alone updates neither the app registration nor the pinned Function reference.** +Reuse leaves an external vault's issuance policy unchanged; automatic issuance there still +requires manual endpoint synchronization (`certificateRenewal: manual`). Account for every +endpoint sharing the key and cached/in-flight requests; decryption uses one in-process private key. **Legacy deployments require an explicit decision.** A compatible matching Key Vault certificate can be reused even if it has an older/custom subject. A standalone PEM secret without a Key Vault @@ -580,23 +548,18 @@ follow [safe recovery](Troubleshooting.md#recover-without-destructive-cleanup). ### Source versioning -**For developers testing unpublished changes:** publish to a public fork with a matching stable -package release, then use `-SourceRepository ` and -`-SourceRef `. Both must identify the same source as the downloaded -launcher. Use `-PackageReleaseTag` if the fork contains more than one stable package release. -Unpublished worktree changes are not downloadable from GitHub. - -`-SourceRepository` defaults to `Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample`. -The small entry point resolves `-SourceRef` (default `main`) to a single commit in that repository. All supporting -PowerShell, Bicep, both catalogs, and every catalog-listed provider profile are downloaded from that commit. -The package catalog supplies asset names, while setup resolves the latest stable `epp-packages-*` -release from the same repository and verifies the selected asset against that release's -`SHA256SUMS.txt`. The plan and saved summary record the concrete versioned URL and hashes. -For a fully repeatable deployment, use both a reviewed full commit SHA and -`-PackageReleaseTag epp-packages--`. Provider JSON selects data only; it cannot -redirect execution to another script. Download failures stop setup, and temporary downloads are -removed on completion or failure. Select only a repository whose code you trust: its supporting -PowerShell is executed locally. +Defaults are `-SourceRepository Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample` and +`-SourceRef main`. Setup resolves one commit for supporting PowerShell, Bicep, both catalogs, +and all catalog-listed profiles. The package catalog names assets from that repository's latest +stable `epp-packages-*` release; `SHA256SUMS.txt` verifies them. The plan/summary records URLs and hashes. + +For repeatable deployment, pin a reviewed full SHA **and** +`-PackageReleaseTag epp-packages--`. To test custom source, publish a public fork and +matching stable release; align both source options with the downloaded launcher. Local unpushed +changes are not downloadable. + +Trust the selected repository: its PowerShell executes locally. Provider JSON selects data, not +another executable script. Downloads fail closed and temporary files are removed on success/failure. ### Offline setup checks diff --git a/setup/docs/Troubleshooting.md b/setup/docs/Troubleshooting.md index 1c27743..76f8ba4 100644 --- a/setup/docs/Troubleshooting.md +++ b/setup/docs/Troubleshooting.md @@ -1,17 +1,30 @@ # Setup and onboarding troubleshooting -Find your symptom in the [triage table](#after-deployment-triage), then follow the matching checks. -If the setup script stopped, start with [safe recovery](#recover-without-destructive-cleanup) -before rerunning it. A successful deployment does not yet prove provider access or delivery. +[Documentation](../../docs/README.md) | [Setup reference](README.md) | [Resume onboarding](../../docs/GETTING-STARTED.md) -Return to the [five-step setup guide](../../docs/GETTING-STARTED.md) when you are ready to continue onboarding. +Choose the failing stage below. **Do not delete resources or blindly retry a live send.** +Before rerunning setup, read [safe recovery](#recover-without-destructive-cleanup). +A completed deployment does not prove provider access or delivery. + +## Find your symptom + +| Stage / symptom | Start here | +| --- | --- | +| Account missing or wrong tenant | [Account picker](#the-expected-cached-account-or-subscription-is-missing), [directory context](#azure-cli-directory-context-does-not-match-the-selected-tenant) | +| Permission or prerequisite failure | [Preflight](#authentication-permission-or-runtime-preflight-fails), [Graph 403](#az-ad-or-az-rest-returns-403-forbidden), [PrincipalNotFound](#principalnotfound-for-the-azure-operator) | +| Region or hosting rejected | [Quota](#deployment-fails-with-subscriptionisoverquotaforsku), [plan selection](#service-plan-selection-or-availability-fails), [FC1 object-reference error](#fc1-deployment-preflight-reports-an-object-reference-error), [EP1 location check](#appservice-list-locations-rejects-ep1) | +| Certificate or resource registration fails | [Certificate selection](#certificate-selection-reuse-or-cleanup-fails), [resource providers](#a-required-azure-resource-provider-is-not-registered) | +| Download, build, or parser failure | [Old package prompts](#setup-still-asks-for-packageurl-or-packagesha256), [checksum/download](#a-checksum-or-package-download-fails), [.NET](#net-build-fails), [Python](#python-remote-build-fails), [CLI JSON](#azure-cli-warnings-break-json-parsing) | +| Deployment stops after approval | [Publication and startup](#deployment-stops-after-approval) | +| Endpoint rejects calls, does not deliver, or has no logs | [After-deployment triage](#after-deployment-triage), [401/live delivery](#the-endpoint-returns-401-or-live-delivery-fails), [cache settings](#credential-caching-does-not-match-the-selected-plan) | +| Optional regional routing fails | [Front Door checks](#optional-front-door-deployments) | ## After-deployment triage -Keep the timestamped deployment summary and original error. Record the intended tenant, -subscription, resource group, runtime/release, test UTC time, and safe correlation IDs in a private -support case. Never attach complete app settings, tokens, phone numbers, OTPs, private keys, -encrypted request bodies, or raw provider responses. Do not enable body tracing for diagnosis. +Keep the original error and timestamped summary. In a private support case, record tenant, +subscription, resource group, runtime/release, UTC test time, and safe correlation IDs. +Never attach complete settings, tokens, phone numbers, OTPs, private keys, encrypted bodies, or +raw provider responses. Keep body tracing off. | Symptom | First checks / owner | Do not do this | |---|---|---| @@ -26,35 +39,30 @@ encrypted request bodies, or raw provider responses. Do not enable body tracing | Requests exist but Python/.NET event query is empty | Use [runtime-specific discovery](../../docs/MONITORING.md#runtime-specific-log-discovery). Python extras/.NET scopes may not be exported; .NET host OpenTelemetry does not initialize direct worker export. | Do not apply JavaScript's JSON parser to other runtimes or promise complete dependencies/exceptions. | | No expiry email or alert | Operations owner checks vault certificate contacts/lifetime policy, independently configured reminders/action groups/rules, and notification tests. | Do not assume setup created alerts, or diagnostic settings deliver Key Vault expiry events. | -An accepted provider response, HTTP 200, a matching evaluation nonce, and actual recipient -delivery are different evidence. Retain only nonce-match booleans, statuses, latency, safe support -IDs, and delivery-confirmation outcomes. [Run the complete acceptance checks](../../docs/ONBOARDING.md#validate-the-deployed-endpoint) -before activation. +Keep nonce-match **booleans**, statuses, latency, safe IDs, and delivery-confirmation outcomes. +Evaluation success, provider acceptance, and recipient delivery are different evidence. +[Complete acceptance checks](../../docs/ONBOARDING.md#validate-the-deployed-endpoint) before activation. ## Recover without destructive cleanup -Before approval, setup's read-only checks can fail without Azure resource mutation (Bicep -installation and sign-in may already have occurred). After approval, partial Azure/Entra changes -can remain even if no success summary was written. Inspect the Azure subscription/resource-group -deployment operations and the original failure before retrying. - -Correct the underlying error, then use the same reviewed tenant, subscription, app, runtime, -plan, prefix, provider/route, and intended source/package versions for a recovery rerun. -Changing identifiers can create new resources or break certificate continuity; changing a route -can reconfigure the existing endpoint. Setup does not resume from a checkpoint or undo previous -steps. For an active endpoint, arrange a change window because reruns can disable ingress and -update app settings/Entra. - -Do not delete a resource group, purge/recreate the vault, remove encryption credentials, or revoke -shared Microsoft service-principal grants as a generic retry strategy. Use the separate -[rollback and decommissioning procedure](README.md#rollback-and-decommissioning) if retirement -is actually intended. +1. **Locate the failure.** Before approval, checks do not mutate Azure resources, although sign-in + or Bicep installation may have occurred. After approval, partial Azure/Microsoft Entra ID changes + can remain without a summary. Inspect **Azure portal > subscription/resource group > Deployments** + and the original error. +2. **Fix, then rerun with the same reviewed inputs:** tenant, subscription, app, runtime, plan, + prefix, provider/route, and source/package versions. Changed IDs can create resources or break + key continuity; a changed route can reconfigure the endpoint. There is no checkpoint resume or + automatic undo. Active endpoints need a change window because reruns can disable ingress and + change settings. +3. **Verify the failed stage and complete acceptance checks.** Never use resource-group deletion, + vault purge/recreation, encryption-credential removal, or shared-grant revocation as a retry. + Retirement uses the separate [rollback procedure](README.md#rollback-and-decommissioning). ## Certificate selection, reuse, or cleanup fails -Setup checks the application's encryption credentials before approval. With existing credentials, -choose **Reuse** and a certificate key ID/location, or **Replace**; it never silently adds another -encryption certificate on a rerun. See [certificate selection](README.md#encryption-certificate-selection). +**Check:** compare the application key ID and exact vault version using +[certificate selection](README.md#encryption-certificate-selection). +Existing credentials require explicit **Reuse** or **Replace**, not an automatic new certificate. | Failure | Safe recovery | |---|---| @@ -66,16 +74,14 @@ encryption certificate on a rerun. See [certificate selection](README.md#encrypt | Credentials or the source vault's permission model changed after approval | Stop competing changes and rerun selection/approval against the current state. Do not force an outdated cleanup plan. | | An encryption credential has an unsupported type, or a non-encryption credential cannot be read | Have the application owner review the credential and read permissions. Setup cannot safely preserve or interpret it and will not blindly reset the credential collection. | -New certificates are prepared before old application entries are removed. A failed reference or -source-vault access check leaves the registered encryption credentials intact, although Azure -resources/app settings may already have changed. If registration succeeded and a later publication -step failed, inspect the current app and choose **Reuse** with the certificate ID printed by setup; -repeating **Replace** would request another rotation. Keep the same resource prefix for recovery. +**Recover:** a failed key-reference/source-vault check leaves registered encryption credentials +intact, but resources/settings may have changed. If registration already succeeded, choose **Reuse** +with the printed certificate ID and the same prefix; another **Replace** requests another rotation. -An approved cleanup removes only the listed `AsymmetricX509Cert` / `Encrypt` entries from the -application. It never deletes vault certificates, keys, secrets, or unrelated application -credentials. Coordinate maintenance and authorized evaluation for every active endpoint sharing -the application; cached or in-flight requests can still use a retired key. +**Expected:** one approved application encryption credential. Cleanup removes only approved +`AsymmetricX509Cert` / `Encrypt` entries, not vault objects or unrelated credentials. +Coordinate maintenance and evaluation for every endpoint sharing the app; cached/in-flight +requests can still use a retired key. ## Optional Front Door deployments @@ -95,10 +101,8 @@ Use the [manual Front Door guide](../../docs/FRONTDOOR.md) for that option. ## Deployment fails with SubscriptionIsOverQuotaForSku -This is a subscription quota check, not an authentication failure or a missing resource-provider -registration. A region can support Linux Premium EP1 while your subscription has an **EP1 VMs** -limit of zero there. Quotas are regional: an allowance in one region does not establish an -allowance in another. +**Check:** the selected subscription's regional **EP1 VMs** quota, not authentication or resource +registration. A supported Linux EP1 region can still have zero quota for your subscription. 1. Confirm the tenant, subscription, region, and SKU in the deployment plan and Azure error. 2. In the Azure portal, open **Quotas**, select **App Service**, and filter to the intended @@ -113,60 +117,49 @@ allowance in another. 5. Alternatively, choose another region only after checking its quota, service availability, and your residency and provider requirements. Review the updated deployment plan before approval. -See the [Azure quotas overview](https://learn.microsoft.com/azure/quotas/quotas-overview) for the -quota-management and support options. Setup does not request or guarantee a quota increase. -Do not change the hosting SKU, disable Easy Auth, or change provider credentials to bypass this -error. After resolving quota, rerun setup and complete the normal deployed validation checks. +**Expected:** sufficient effective quota, not merely a submitted request. Setup cannot guarantee +an increase. See [quota support](https://learn.microsoft.com/azure/quotas/quotas-overview). +Do not change SKU, authentication, or provider credentials to bypass the error; rerun with reviewed +inputs after quota is resolved, then validate the endpoint. ## Service plan selection or availability fails -Choose `-ServicePlan FC1` for Flex Consumption or `-ServicePlan EP1` for Premium. The parameter is -required in noninteractive mode. FC1 requires Azure CLI **2.60.0+**, while EP1 keeps **2.48.1+**. -FC1 regional checks use `az functionapp list-flexconsumption-locations`; EP1 uses its existing ARM -check below. Availability in one plan does not imply availability in the other. - -FC1 offers a free usage grant, not a zero-cost guarantee. It configures zero always-ready instances; -supporting services and usage beyond the grant can still incur charges. +**Check:** `-ServicePlan FC1` needs Azure CLI **2.60.0+**; `EP1` needs **2.48.1+**. +The parameter is required unattended. FC1 availability uses `az functionapp list-flexconsumption-locations`; +EP1 uses the [ARM check below](#appservice-list-locations-rejects-ep1). Availability is plan-specific. +FC1 has zero always-ready instances and a usage grant, not a zero-cost guarantee. -Setup refuses to switch an existing prefix between FC1 and EP1. Use a different prefix and validate -the new endpoint before manually changing policy. For an active endpoint, also onboard a new -dedicated application or coordinate key continuity. A new prefix still requires the existing -application's certificate reuse/replacement decision; choose the existing Key Vault certificate -to retain its key rather than inadvertently rotating every endpoint sharing that application. -For older deployments without a service-plan tag, -preflight checks the actual hosting-plan SKU. Use `-ServicePlan EP1` when rerunning an existing EP1 -deployment; do not delete tags to bypass the migration guard. +**Fix:** follow [plan selection and migration rules](README.md#service-plan-selection). +In-place FC1/EP1 switching is unsupported: use a different prefix, plus a dedicated app or an approved +key-continuity plan, and validate before policy changes. A new prefix does not authorize key rotation. +Existing EP1 deployments use `-ServicePlan EP1`; setup checks actual SKU when tags are absent. +Never remove tags to bypass this guard. ## FC1 deployment preflight reports an object-reference error -An affected setup template can fail with `InvalidTemplateDeployment` against +**Symptom:** `InvalidTemplateDeployment` against `Microsoft.Web/serverfarms` and the inner message `Object reference not set to an instance of an object`. -The cause is the Function App's entire `properties` object being wrapped in a `union()` expression -that contains the Flex deployment storage endpoint's runtime `reference()`. ARM defers that whole -expression, including `serverFarmId`, so the provider cannot see the Function-to-plan association -during preflight. This error is not evidence that the FC1 SKU or selected region is unsupported. - -The corrected Bicep keeps `properties` as a literal object with an explicit `serverFarmId` and makes -only `functionAppConfig` conditional on FC1. The storage endpoint lookup, hosting-plan configuration, -scaling, managed identities, authentication, and public-access settings are unchanged. - -Download the updated `Setup-Epp.ps1` from the intended source branch and rerun it with matching -`-SourceRepository` and `-SourceRef ` values. A command pinned to an -older commit still downloads the old support files and template. Keep the original tenant, subscription, application, -language, service plan, and resource prefix rather than changing plans to bypass this error. -`az deployment sub validate` can check the corrected template without creating resources. Passing -validation does not prove successful publication, runtime startup, Easy Auth enforcement, or delivery. +An older template wraps all Function `properties` in `union()` with a storage `reference()`. +ARM defers `serverFarmId` with it, hiding the Function-to-plan association during preflight. +This does not establish that FC1 or the region is unsupported. + +**Fix:** download the corrected launcher with matching `-SourceRepository` and +`-SourceRef `. The fixed template keeps literal `properties` and +explicit `serverFarmId`, with only `functionAppConfig` conditional; storage lookup, scaling, +identity, authentication, and ingress behavior are unchanged. An old pinned commit still loads the old template. +Keep the original deployment inputs rather than switching plans. + +**Verify:** `az deployment sub validate` checks the template without creating resources. +It does not verify publication, startup, Easy Auth, or delivery. ## Credential caching does not match the selected plan -Check `EPP_KEY_VAULT_CACHE_ENABLED` and `EPP_ACCESS_TOKEN_CACHE_ENABLED` on the serving Function App. -Setup writes both as `"false"` for FC1 or `"true"` for EP1; rerunning setup restores those values. -These settings only affect a deployed Function package that implements the cache readers. -Deploy a supporting runtime release and restart after edits; older packages ignore the switches. -Values must be `true` or `false` (case-insensitive, trimmed); unset defaults to enabled. -Invalid selected values fail live credential acquisition closed without blocking evaluation. -Only the selected provider's authentication mode chooses the switch; disabling it skips startup -preparation and cross-request reuse, not live credential acquisition or the platform's decryption-key reference. +**Check:** serving-app `EPP_KEY_VAULT_CACHE_ENABLED` and `EPP_ACCESS_TOKEN_CACHE_ENABLED`. +Setup writes both as `"false"` for FC1 or `"true"` for EP1 and restores them on rerun. +**Fix:** deploy a supporting release and restart after edits; older packages ignore the switches. +The [cache contract](../../docs/CONTRACT.md#credential-caching-and-refresh) defines accepted values, +fail-closed invalid settings, and runtime differences. Disabling a cache does not eliminate live +credential acquisition or affect the platform's decryption-key reference. ## appservice list-locations rejects EP1 @@ -201,12 +194,10 @@ the script does not unregister services that other workloads might now use. ## Azure CLI directory context does not match the selected tenant -Setup selects an Azure subscription without changing the Azure CLI default tenant. Microsoft -Graph operations use that CLI directory through `az ad` and `az rest`, so `az account show` must -describe a public-cloud **user** session in the selected tenant. - -In interactive setup, choose **Refresh subscriptions from Azure** or **Sign in to Azure -(device code)** from the account picker. You can also authenticate before rerunning: +**Check:** `az account show` must identify an AzureCloud **user** session in the selected tenant. +Setup does not change the CLI default tenant; Graph uses that directory through `az ad`/`az rest`. +**Fix:** choose **Refresh subscriptions from Azure** or **Sign in to Azure (device code)**, +or authenticate before rerunning: ```powershell az login --tenant '' --use-device-code @@ -240,32 +231,25 @@ interactive device-code flow. ## Setup still asks for PackageUrl or PackageSha256 -You are running an older launcher or source revision. Download `Setup-Epp.ps1` again and supply -the intended `-SourceRepository` and `-SourceRef`. The current version asks for **one language** -and reads its package URL and published checksum automatically. Remove old package URL/hash -arguments from saved commands. +**Fix:** refresh `Setup-Epp.ps1` and its reviewed `-SourceRepository`/`-SourceRef`; remove old +URL/hash arguments. **Expected:** one language choice, with package URL/checksum read automatically. ## A checksum or package download fails -Setup resolves the latest stable `epp-packages-*` CI release by default, then downloads the selected -catalog asset and that release's `SHA256SUMS.txt`. Use `-PackageReleaseTag` when reproducing a -specific release. The checksum file must contain exactly one valid entry for that asset. Missing, -duplicate, malformed, or mismatched checksums fail closed; there is no manual-hash or -skip-verification workaround. Verify the release assets and your access to GitHub. +**Check:** GitHub access, the selected `epp-packages-*` release, asset, and `SHA256SUMS.txt`. +Exactly one valid checksum entry must match; missing, duplicate, malformed, or mismatched entries +fail closed. **Never bypass verification or supply a replacement hash.** -Supporting tools, Bicep, catalogs, and provider JSON all come from the commit selected at startup. -For a public-fork branch, pass both source options. A full commit SHA avoids branch-resolution -API rate limits. Private repositories are not supported by these unauthenticated raw downloads. +**Fix:** align the launcher and [source/package versions](README.md#source-versioning). +Pin `-PackageReleaseTag` to reproduce a release. Public-fork branches need both source options; +a full SHA avoids branch-resolution API rate limits. Private raw downloads are unsupported. ## .NET build fails -Install the **.NET 8 SDK** and allow NuGet access. Setup selects an installed 8.x SDK, extracts the -verified source into its temporary workspace, runs a Linux-targeted Release publish, checks the -publish output, and creates the ready ZIP. The source ZIP is not uploaded as runnable code. -Build failures occur before Azure resource creation and include the `dotnet` failure output. - -Do not manually replace the published source checksum with a hash of the build output. These -represent different artifacts; setup computes the built artifact's hash itself. +**Fix:** install the **.NET 8 SDK**, allow NuGet access, and inspect the reported `dotnet` error. +**Expected:** setup selects 8.x, publishes verified source for Linux in its temporary workspace, +checks output, and packages it before Azure resource creation. Source ZIPs are not runnable. +Do not replace the source checksum with the built ZIP hash; setup calculates that separately. ## Python remote build fails @@ -292,38 +276,24 @@ immediate administrator inspection. Do not bypass certificate errors or enable b ## Azure CLI warnings break JSON parsing -The current helper separates stdout from stderr. Successful command JSON is parsed independently -of SDK warnings, while stderr warnings are shown and nonzero exit codes still fail. Upgrade an -older downloaded helper by refreshing the launcher/source revision. +**Fix:** refresh the reviewed launcher/source revision. +**Expected:** stdout JSON is parsed separately from visible stderr warnings; nonzero exits still fail. ## Authentication, permission, or runtime preflight fails -Use Azure Cloud Shell in PowerShell mode, or PowerShell 7 with Azure CLI and Bicep. The Microsoft -Graph PowerShell SDK is not used. Sign in to the customer tenant with a user account. ARM requests -use the selected subscription; setup does not change the CLI default subscription or adopt -unrelated resource groups. Directory requests reuse the same CLI session through `az ad` and -`az rest`. - -Azure CLI itself must be installed before setup. Interactive setup can select cached user -subscriptions, refresh them, or start tenant-specific device-code sign-in. Only the Azure CLI -Bicep component can be installed after confirmation. In unattended runs, preinstall Bicep or pass -`-InstallPrerequisites`, and establish the matching CLI user session before starting setup. - -Only the dedicated customer application registration must exist from manual Step 1. After approval, -setup makes it multi-tenant, restricts it to its home tenant plus the provider JSON's `tenantId` -and existing allowed provider tenants through the Entra allowed-tenants preview, creates both -required service principals, adds and assigns -`Epp.Invoke`, and grants the Microsoft phone-provider service principal Graph `Application.Read.All`. - -Azure subscription roles do not authorize Microsoft Graph. The signed-in CLI user must be able to -read and update the application and service principals and create app-role assignments. Granting -Microsoft Graph `Application.Read.All` to the Microsoft phone-provider service principal normally -also requires an active Privileged Role Administrator role. Noninteractive runs must establish -the CLI context first and supply `-ApproveDeployment` separately. - -The tenant restriction uses Microsoft Graph beta `signInAudienceRestrictions`. If that preview is -unavailable or the tenant policy blocks it, setup stops before mutation rather than silently allowing -all organizational tenants. +1. Check [tools and permissions](README.md#prerequisites-for-step-2): PowerShell 7/Cloud Shell, + installed Azure CLI, and a customer-tenant **user** session. Graph reuses that session; no Graph + PowerShell SDK. ARM uses the selected subscription without adopting unrelated resource groups + or changing the CLI default subscription. +2. Resolve missing Bicep through its interactive confirmation, or preinstall/use + `-InstallPrerequisites` unattended. Azure CLI itself is not installed by setup. +3. Verify the [dedicated app and approved grants](README.md#step-1---manually-create-the-application). + Azure roles do not grant Graph permissions; the user needs application/service-principal + management and app-role assignment permissions, including active Privileged Role Administrator + for the approved `Application.Read.All` grant. +4. Confirm beta `signInAudienceRestrictions` availability. If tenant policy/preview access blocks it, + stop; setup does not silently allow all tenants. Unattended runs still need the user session + and separate `-ApproveDeployment`. ## `az ad` or `az rest` returns 403 Forbidden @@ -366,9 +336,9 @@ app to another runtime with the same prefix. ## Deployment stops after approval -Some resources can remain. No automatic deletion, vault purge/recovery, policy activation, or -rollback occurs. Inspect the named Azure deployment and the reported error, then rerun with the -same tenant, subscription, application, language, service plan, and prefix after correcting it. +**Check:** the named deployment and original error. Partial resources remain; setup does not +delete, purge/recover vaults, activate policy, or roll back. Follow +[safe recovery](#recover-without-destructive-cleanup) before a same-input rerun. Recognized storage/Key Vault RBAC propagation errors are retried for at most twelve attempts. Transient Function startup errors also have bounded retries. This includes the specific ARM @@ -378,23 +348,23 @@ return while a newly restarted host is still loading an otherwise valid package. `SendOtp` must appear in Azure's function metadata. No success summary is written if publication or registration fails. -If setup exhausts the retries, inspect Application Insights for host initialization, worker startup, -and function discovery errors before rerunning. The expected healthy sequence includes `Worker process -started and initialized`, `Found the following functions: Host.Functions.SendOtp`, and `Job host -started`. Setup closes public ingress after a persistent publication failure. +**Verify:** inspect [host/worker logs](../../docs/APPLICATION-INSIGHTS.md#single-region-configuration) +for startup/discovery failures. Expected messages include `Worker process started and initialized`, +`Found the following functions: Host.Functions.SendOtp`, and `Job host started`. +Setup closes public ingress after persistent publication failure. ## The endpoint returns 401 or live delivery fails -Keep Easy Auth enabled. Check the trusted tenant, actual token version, audience, HTTPS requirement, -and nonempty Microsoft caller allowlist. Keep `tokenEncryptionKeyId` null on the endpoint app; -payload JWE encryption is separate from signed bearer-token validation. - -For live delivery, confirm the request's channel-specific provider URL -(`EPP_PROVIDER_SMS_ENDPOINT` or `EPP_PROVIDER_VOICE_ENDPOINT`) and its account authorization. -The matching setting is required; missing, empty, or invalid URLs fail closed without using a -shared or other-channel endpoint. Use a package that supports these settings. -Telesign needs its exact Key Vault secret names; Soprano needs provider-side authorization, not -API-key secrets. Arrange an authorized evaluation through the -[test handoff](../../docs/ONBOARDING.md#validate-the-deployed-endpoint) before controlled live -messages. EPP policy remains a separate assisted administrator-approved operation; no setup code -updates it. Public Graph SMS/voice schemas do not document the EPP activation fields. +1. **Before invocation:** use the [Easy Auth checklist](../../docs/ONBOARDING.md#manual-deployment-trust-checklist). + Check issuer, token version, audience, HTTPS, and the nonempty Microsoft caller allowlist. + Keep Easy Auth enabled and app `tokenEncryptionKeyId` null; bearer validation and payload JWE are separate. +2. **After invocation:** correlate [runtime events](../../docs/MONITORING.md#runtime-specific-log-discovery). + Confirm `EPP_PROVIDER_SMS_ENDPOINT` or `EPP_PROVIDER_VOICE_ENDPOINT` and account access. + The selected URL is required and has no shared/other-channel fallback; deploy a supporting package. +3. Complete [provider authentication](../../docs/ONBOARDING.md#complete-provider-authentication): + exact vault secrets for Telesign, provider-side authorization for Soprano. + Arrange [authorized evaluation, then controlled live checks](../../docs/ONBOARDING.md#validate-the-deployed-endpoint). + Verify acceptance and receipt separately; never blindly resend a timeout. + +**Activation remains separate:** use the [assisted policy procedure](README.md#step-3---manually-validate-and-activate-policy). +Setup does not change policy, and public Graph SMS/voice schemas do not document the EPP fields. From 02944d8a0c827f7de88de9eeea2c97a0934dfe5f Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 13:34:21 -0700 Subject: [PATCH 04/13] docs: add task-oriented navigation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 21 ++++++++++++++------- docs/README.md | 35 ++++++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 7accc90..3b68d9f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ production certification. Before deploying, [confirm supported provider access a approved testing and activation procedure](docs/GETTING-STARTED.md#1-get-ready). If either is unavailable, stop. Azure resources and provider services can incur charges. +## Table of Contents + +- [Architecture](#architecture) +- [Get started](#get-started) +- [Guidance](#guidance) + ## Architecture ![Microsoft Entra ID sends an encrypted request through Easy Auth to a Function, which uses Key Vault and calls the phone provider](docs/images/single-region-architecture.png) @@ -32,12 +38,13 @@ Follow the **[five-step setup guide](docs/GETTING-STARTED.md)** in order: See the [sample's production limitations](docs/CONTRACT.md#production-limitations). -## References +## Guidance -- [Setup reference](setup/docs/README.md) and [troubleshooting](setup/docs/Troubleshooting.md) -- [Provider configuration and validation](docs/ONBOARDING.md) -- [Monitoring](docs/MONITORING.md), [Microsoft Entra ID sign-in alerts](docs/SIGN-IN-MONITORING.md#recommended-monitors), and [SMS cost estimation](docs/COST-CALCULATOR.md) -- [Technical reference](TECHNICAL.md), [HTTP contract](docs/CONTRACT.md), and [BYOT integration](docs/BRING-YOUR-OWN-PROVIDER.md) +- **Deploy and validate:** [Five-step setup](docs/GETTING-STARTED.md) +- **Configure:** [Setup reference](setup/docs/README.md) and [provider configuration](docs/ONBOARDING.md) +- **Troubleshoot:** [Setup and endpoint failures](setup/docs/Troubleshooting.md) +- **Operate:** [Monitoring, identity, renewal, rollback, and costs](docs/README.md#operate) +- **Develop:** [Runtimes, contract, logging, and provider integration](docs/README.md#develop) +- **Extend:** [Optional multi-region and fallback designs](docs/README.md#optional-designs) -Use the [documentation index](docs/README.md) for deployment, troubleshooting, operations, and development. -Optional multi-region and provider-fallback designs are not built-in guided setup features. +See the [documentation index](docs/README.md) for all guides. diff --git a/docs/README.md b/docs/README.md index 654a640..db0358c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# Documentation +# Documentation by task [Sample overview and architecture](../README.md) @@ -6,6 +6,14 @@ Microsoft's approved test/activation procedure before spending or deploying. Use a dedicated nonproduction environment; deployment does not activate policy. +## Table of Contents + +- [Deploy and validate](#deploy-and-validate) +- [Operate](#operate) +- [Develop](#develop) +- [Optional designs](#optional-designs) +- [Get help](#get-help) + ## Deploy and validate | Task | Guide | @@ -18,22 +26,27 @@ nonproduction environment; deployment does not activate policy. ## Operate -- [Find Function logs and create alerts](MONITORING.md); [diagnose collection](APPLICATION-INSIGHTS.md). -- [Investigate Microsoft Entra ID sign-ins and configure identity alerts](SIGN-IN-MONITORING.md). -- [Renew encryption certificates](../setup/docs/README.md#encryption-certificate-lifecycle) and - [roll back or decommission](../setup/docs/README.md#rollback-and-decommissioning). +- **Function logs and alerts:** [Monitoring](MONITORING.md) +- **Missing or incomplete telemetry:** [Application Insights collection](APPLICATION-INSIGHTS.md) +- **Microsoft Entra ID sign-ins and identity alerts:** [Identity monitoring](SIGN-IN-MONITORING.md) +- **Certificate renewal:** [Certificate lifecycle](../setup/docs/README.md#encryption-certificate-lifecycle) +- **Rollback or decommissioning:** [Rollback procedure](../setup/docs/README.md#rollback-and-decommissioning) ## Develop -- Local setup and source: [JavaScript](../javascript/README.md), [C#](../dotnet/README.md), [Python](../python/README.md). -- [Settings and packages](../TECHNICAL.md), [HTTP contract](CONTRACT.md), and [application log schema](LOGGING.md). -- [Implement the provider-neutral BYOT interface](BRING-YOUR-OWN-PROVIDER.md). -- [Review sample limitations](CONTRACT.md#production-limitations) before production use. +- **Runtime development:** [JavaScript](../javascript/README.md), [C#](../dotnet/README.md), or [Python](../python/README.md) +- **Settings and packages:** [Technical reference](../TECHNICAL.md) +- **Requests and responses:** [HTTP contract](CONTRACT.md) +- **Runtime events:** [Application log schema](LOGGING.md) +- **Provider integration:** [Provider-neutral BYOT interface](BRING-YOUR-OWN-PROVIDER.md) +- **Production gaps:** [Sample limitations](CONTRACT.md#production-limitations) ## Optional designs -[Multi-region Front Door](FRONTDOOR.md) and [primary-to-secondary provider fallback](MULTI-PROVIDER-IMPLEMENTATION.md) -require separate implementation and validation. Neither is enabled by guided setup. +These designs require separate implementation and validation; guided setup enables neither. + +- **Route across regions:** [Multi-region Front Door design](FRONTDOOR.md) +- **Add provider fallback:** [Multi-provider implementation](MULTI-PROVIDER-IMPLEMENTATION.md) ## Get help From 383a085fddb7abbfbf5003a8680b1d9b5b006db0 Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 13:44:25 -0700 Subject: [PATCH 05/13] docs: expand task-based README guidance Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 80 +++++++++++++++++++++++++++++++++++++++++++------- docs/README.md | 8 +++++ 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3b68d9f..63aa9be 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,42 @@ Connect Microsoft Entra ID to your phone provider for SMS and voice one-time pas Deploy **one endpoint in one Azure region**, using JavaScript, C#, or Python. Guided setup handles Azure resources and code publication; no repository clone is needed. -**Start in a dedicated nonproduction tenant and subscription.** This is a sample, not -production certification. Before deploying, [confirm supported provider access and Microsoft's -approved testing and activation procedure](docs/GETTING-STARTED.md#1-get-ready). -If either is unavailable, stop. Azure resources and provider services can incur charges. +**Start in a dedicated nonproduction tenant and subscription.** This sample is not production +certification. Review [requirements](#requirements) before purchasing or deploying. ## Table of Contents +- [Features](#features) +- [Requirements](#requirements) - [Architecture](#architecture) - [Get started](#get-started) - [Guidance](#guidance) + - [Deploy and configure](#deploy-and-configure) + - [Operate](#operate) + - [Develop](#develop) + - [Optional designs](#optional-designs) +- [Getting help](#getting-help) + +## Features + +- One Azure Function endpoint that accepts Microsoft Entra ID SMS and voice requests and calls the configured provider. +- JavaScript, C#, and Python implementations of the same [HTTP and trust contract](docs/CONTRACT.md). +- Guided setup for Azure resources, package verification, publication, and Easy Auth configuration. +- Evaluation requests that authenticate and decrypt without sending a message to the provider. +- Monitoring guidance for Function requests, runtime events, Microsoft Entra ID sign-ins, and telemetry collection. +- A provider-neutral [bring-your-own-telephony integration path](docs/BRING-YOUR-OWN-PROVIDER.md). + +## Requirements + +Before deploying: + +- Confirm [supported provider access and Microsoft's approved testing and activation procedure](docs/GETTING-STARTED.md#1-get-ready). + If either is unavailable, stop. +- Use a dedicated nonproduction tenant, subscription, application registration, and resource prefix. +- Check the required [administrator roles, Azure permissions, tools, regional availability, and quota](setup/docs/README.md#prerequisites-for-step-2). +- Plan for Azure resource, telemetry, and provider charges. Use the [provider cost estimate](docs/COST-CALCULATOR.md) as planning input, not a billing report. + +Deployment does not activate policy. Keep Easy Auth enabled; never bypass caller validation to test. ## Architecture @@ -40,11 +66,43 @@ See the [sample's production limitations](docs/CONTRACT.md#production-limitation ## Guidance -- **Deploy and validate:** [Five-step setup](docs/GETTING-STARTED.md) -- **Configure:** [Setup reference](setup/docs/README.md) and [provider configuration](docs/ONBOARDING.md) -- **Troubleshoot:** [Setup and endpoint failures](setup/docs/Troubleshooting.md) -- **Operate:** [Monitoring, identity, renewal, rollback, and costs](docs/README.md#operate) -- **Develop:** [Runtimes, contract, logging, and provider integration](docs/README.md#develop) -- **Extend:** [Optional multi-region and fallback designs](docs/README.md#optional-designs) +### Deploy and configure + +- [Five-step setup](docs/GETTING-STARTED.md) for a first deployment. +- [Customer configuration and validation](docs/ONBOARDING.md) for IDs, provider authentication, testing, and activation. +- [Setup reference](setup/docs/README.md) for permissions, hosting plans, certificate choices, unattended use, renewal, and rollback. +- [Troubleshooting](setup/docs/Troubleshooting.md) for symptom-led checks and safe recovery. + +### Operate + +- [Function monitoring and alerts](docs/MONITORING.md) +- [Application Insights collection diagnostics](docs/APPLICATION-INSIGHTS.md) +- [Microsoft Entra ID sign-in monitoring](docs/SIGN-IN-MONITORING.md) +- [Certificate renewal](setup/docs/README.md#encryption-certificate-lifecycle) and [rollback](setup/docs/README.md#rollback-and-decommissioning) +- [SMS and voice cost estimation](docs/COST-CALCULATOR.md) + +### Develop + +- Runtime guides: [JavaScript](javascript/README.md), [C#](dotnet/README.md), and [Python](python/README.md) +- [Architecture, settings, and packages](TECHNICAL.md) +- [HTTP, status, configuration, and runtime contract](docs/CONTRACT.md) +- [Application log events and safe fields](docs/LOGGING.md) +- [Provider-neutral BYOT integration](docs/BRING-YOUR-OWN-PROVIDER.md) + +### Optional designs + +- [Multi-region routing with Azure Front Door](docs/FRONTDOOR.md) +- [Conservative primary-to-secondary provider fallback](docs/MULTI-PROVIDER-IMPLEMENTATION.md) + +Guided setup enables neither optional design. Each requires separate implementation, capacity +planning, validation, operations, and rollback. + +## Getting help + +- For provider access, approved testing, or activation, use the + [provider and Microsoft support route](docs/ONBOARDING.md#before-purchasing-or-deploying). +- For setup or endpoint failures, start with [troubleshooting](setup/docs/Troubleshooting.md). +- For repository problems, use [GitHub issues](https://github.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/issues). -See the [documentation index](docs/README.md) for all guides. +Keep credentials, phone data, OTPs, encrypted bodies, and raw provider responses out of issues +and support requests. See the [complete documentation index](docs/README.md). diff --git a/docs/README.md b/docs/README.md index db0358c..1166cfa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,12 +8,20 @@ nonproduction environment; deployment does not activate policy. ## Table of Contents +- [Understand the sample](#understand-the-sample) - [Deploy and validate](#deploy-and-validate) - [Operate](#operate) - [Develop](#develop) - [Optional designs](#optional-designs) - [Get help](#get-help) +## Understand the sample + +- **Architecture and implementation choices:** [Technical reference](../TECHNICAL.md) +- **Requests, responses, settings, and required behavior:** [HTTP contract](CONTRACT.md) +- **Runtime events and safe diagnostic fields:** [Application log reference](LOGGING.md) +- **Security and production boundaries:** [Production limitations](CONTRACT.md#production-limitations) + ## Deploy and validate | Task | Guide | From f5cc8e2e873154af93d9e6f309dc26513b167504 Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 13:49:35 -0700 Subject: [PATCH 06/13] docs: tighten operational and runtime guidance Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- docs/FRONTDOOR.md | 88 +++++++++++------------------------ docs/ONBOARDING.md | 16 +++---- docs/SIGN-IN-MONITORING.md | 86 +++++++++++----------------------- dotnet/README.md | 50 +++++++------------- javascript/README.md | 27 +++++------ python/README.md | 31 +++++------- setup/docs/README.md | 43 +++++++---------- setup/docs/Troubleshooting.md | 41 +++++++--------- 8 files changed, 136 insertions(+), 246 deletions(-) diff --git a/docs/FRONTDOOR.md b/docs/FRONTDOOR.md index 41d8c89..65d6701 100644 --- a/docs/FRONTDOOR.md +++ b/docs/FRONTDOOR.md @@ -321,66 +321,34 @@ rerouting; it is not permission to disrupt production or a claim of a full Azure test. Define numeric pass/abort thresholds and durations before starting, rather than adopting the observed failover timings below as an SLA. -1. **Assign ownership and approve the boundary.** Name a drill lead, change approver, regional - resource owners, load/telemetry observer, and restoration owner with a backup. Record the exact - tenant, subscription, Front Door profile, origin group, Function Apps, target region, change - window, maximum isolation duration, and restoration actions. Confirm no production policy or - real users depend on these resources. Approve only one isolated test origin at a time. -2. **Prepare bounded, non-delivering traffic and independent recovery.** Use an authorized test - caller to send synthetic encrypted `mode: 2` evaluation requests through the shared URL; never - use real OTPs, real recipient data, or live-send mode. Check nonce matches locally, not in shared - logs. Set hard rate, concurrency, request-timeout, total-duration, and cost limits. Place the - controller and evidence capture outside the target region. Test restoration access and arrange - an independent deadline-triggered restoration mechanism or backup operator; the load controller - must not be the only way to end the outage. -3. **Establish the baseline and acceptance gates.** Save the approved origin/routing, authentication, - ingress, package, key-reference, and regional-dependency configuration. Complete the security and - evaluation checks in section 5. Confirm both origins are healthy and actually serving, using - origin-specific diagnostics rather than only the shared readiness URL. Agree the offered peak - rate plus headroom, minimum survivor soak duration, detection/recovery target, allowed failures - and timeouts, latency percentiles, resource/dependency limits, and failback observation window. - Include abort thresholds for an unhealthy survivor, unexpected delivery, security drift, lost - observability, or an approaching isolation deadline. Capture steady-state evidence from every - region and the client before injecting a fault. -4. **Isolate ONE approved origin; leave it enabled in Front Door.** After confirming it is serving - traffic, apply the approved reversible fault, for example stopping that test Function App. - Timestamp the fault request and platform confirmation separately. Keep the other origin healthy - and unchanged. Do not disable the target in Front Door: manual disabling tests control-plane - removal, not health-probe outage detection. Never disable authentication, exempt SendOtp from - Easy Auth, or weaken ingress to make the drill pass. Front Door can route across all origins - when all probes fail; probe state is not a security boundary. -5. **Observe detection and sustained survivor load.** Maintain the bounded combined-peak evaluation - load plus planned headroom through Front Door, without exceeding the approved test ceiling. - Capture client attempts, completed requests, every failed response and transport error, latency, - safe correlation IDs, and timestamps alongside Front Door probe/access diagnostics and every - region's routing, Function, storage, vault, identity, and telemetry signals. Do not treat missing - regional telemetry as zero errors or use delayed health-percentage graphs to time recovery. - Verify the surviving region sustains the required offered rate and agreed criteria for the - full soak period, not merely one successful request. Label this as evaluation-path capacity, - not full live-send capacity; the separate provider/credential evidence required by the capacity - gate remains necessary. Record failures during detection even if later requests succeed; - retries must not hide first-attempt errors. -6. **Restore unconditionally, including on abort.** Stop fault injection and restore the target - at the deadline or any abort threshold, or when the soak completes. Stop synthetic load if - unsafe. Put restoration in the controller's guaranteed cleanup path and retain the independent - safeguard for controller interruption or loss. Restore the saved approved state even when - assertions fail; escalate immediately to the restoration owner if restoration cannot be - confirmed. Do not start another fault or declare success while an origin remains impaired. -7. **Fail back conservatively, then reverse the direction.** Verify the restored origin's own - dependencies, key/credential resolution, readiness, and successful encrypted evaluations. - Expect Front Door to resume routing when its health criteria are met; watch both origins as - traffic returns and avoid immediately changing weights or driving a new surge. Require stable - health, latency, errors, and capacity through the agreed observation window. Confirm all - resources and routing are restored before repeating the same exercise with the other region - isolated. Both directions must meet the evaluation-drill criteria; the full survivor-capacity - gate additionally requires the separate production-representative live-path evidence above. -8. **Record the outcome and gaps.** Retain sanitized configuration/package identifiers, approvals, - workload and limits, expected versus observed routing, fault/probe/client/recovery timestamps, - rates, latency/errors, capacity/dependency evidence, aborts, and restoration confirmation. - Record pass/fail against each agreed criterion, including unmet or unmeasured gates, with - follow-up owners and repeat-test dates. Never attach keys, bearer tokens, request bodies, OTPs, - or nonce values. Repeat after material topology, package, authentication, key, provider, or - capacity changes and at the operator-approved cadence. +1. **Approve the boundary.** Name the drill, change, telemetry, and restoration owners plus a + backup. Record the exact test resources, window, isolation deadline, and restoration steps. + Confirm no production policy or real users depend on them; isolate only one origin at a time. +2. **Prepare safe traffic and recovery.** Use an authorized caller with bounded encrypted + `mode: 2` evaluation traffic—never live sends or real recipient data. Set rate, concurrency, + timeout, duration, and cost limits. Keep the controller/evidence outside the target region and + verify an independent restoration mechanism or backup operator. +3. **Set the baseline and gates.** Save the approved routing, authentication, ingress, package, + key, and dependency state. Confirm both origins are healthy and serving. Agree survivor load, + soak time, recovery target, allowed failures/timeouts, latency/resource limits, failback window, + and abort thresholds before injecting a fault. +4. **Isolate one origin without disabling it in Front Door.** Apply an approved reversible fault, + such as stopping the test Function App, and timestamp request and confirmation separately. + Keep the survivor unchanged. Do not weaken Easy Auth or ingress; probe health is not a security boundary. +5. **Measure detection and survivor behavior.** Maintain the bounded combined-peak evaluation load + plus headroom. Capture every attempt, failure, timeout, latency, safe correlation ID, probe, + routing, and regional dependency signal. Missing telemetry is not success. Meet the full soak + criteria; evaluation-path results do not replace the separate live-path capacity evidence. +6. **Restore on completion or abort.** Stop unsafe load and restore the saved state at the deadline, + any abort threshold, or completion. Cleanup must run even if assertions or the controller fail. + Escalate immediately if restoration cannot be confirmed. +7. **Verify failback, then reverse direction.** Check the restored origin's dependencies, key and + credential resolution, readiness, and evaluation responses. Wait through the agreed stable + observation window before testing the other origin. Both directions must pass. +8. **Record results and gaps.** Keep sanitized approvals, configuration/package identifiers, + workload limits, timestamps, rates, latency/errors, dependency evidence, aborts, and restoration. + Mark each criterion pass/fail/not tested with an owner and retest date. Never attach secrets, + bearer tokens, request bodies, OTPs, or nonces. ### What this drill does not prove diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index 333063f..c12f805 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -71,16 +71,12 @@ same parameters. No parameter accepts a provider API key or private key. use identical Global/EU URLs; Soprano also shares its app ID/scope. Confirm provider processing locations for messages, credentials, logs, and personal data; choose Azure/telemetry locations separately. -**One guided deployment has one provider and accepts both SMS and voice.** Enabling the second -channel does not require another setup run; validate and activate each intended channel separately. -The resource-name suffix uses the subscription ID, app client ID, and prefix, not the provider or -Azure region. Changing provider/region with the same identifiers can reconfigure the existing -endpoint rather than add another one. For an independent provider/region deployment, use a distinct -prefix and a new dedicated app, then validate it before changing policy. Reusing an existing -app requires an explicit certificate reuse/replacement decision before deployment. A different -prefix does not authorize key rotation or isolate other endpoints sharing the same app registration. -The [manual Front Door design](FRONTDOOR.md) requires coordinated same-key regional origins, -not independent setup runs. +**One guided deployment has one provider and both channels.** Validate and activate SMS and voice +separately; a second channel needs no second setup run. The resource suffix uses subscription, +app client ID, and prefix—not provider or region—so changing provider/region with the same IDs can +reconfigure the endpoint. For an independent deployment, use a new app and prefix. Reusing an app +requires an explicit certificate decision and does not isolate other endpoints sharing that app. +The [Front Door design](FRONTDOOR.md) coordinates same-key origins instead of independent setup runs. ### Values setup configures for you diff --git a/docs/SIGN-IN-MONITORING.md b/docs/SIGN-IN-MONITORING.md index b1c663e..051ea18 100644 --- a/docs/SIGN-IN-MONITORING.md +++ b/docs/SIGN-IN-MONITORING.md @@ -503,72 +503,42 @@ reviewed query, baseline, and response plan. ### Validate without generating provider sends -1. Save the action group before **Test**. Keep the test page open until **Success** or - **Failed**; on failure use **View details**, fix, save, and retest. Check recipient - verification and spam filtering. Group read access alone doesn't grant test permission. -2. Compare the saved query with existing portal records in healthy, qualifying, empty, - and delayed windows. Check output units against Measurement. For a temporary - nonproduction rule, use records inside its lookback and a restricted test action group. - Change the query time filter and portal override together, within the alert scan limit. -3. Authorize the test rule identity, enable it, and observe a scheduled alert and notification - in **Monitor > Alerts**. Check resolution after the condition clears, allowing for stateful - timing. Remove the test rule/overrides and restore the intended production settings. -4. Don't lower production thresholds for testing or route tests to production automation. - If needed, use an approved non-phone test sign-in flow. Don't bypass Easy Auth, send live - OTPs, induce repeated wrong codes, or alter production authentication policy. +1. Test the saved action group and confirm the team receives it. Fix **View details** failures + before testing the rule; read access alone may not grant test permission. +2. Run the saved query against healthy, qualifying, empty, and delayed windows. For a temporary + nonproduction rule, use existing records, a restricted action group, and an authorized rule identity. +3. Observe one scheduled alert and its resolution, then remove temporary overrides and restore + the intended settings. Keep the query time filter and portal override aligned. +4. Never lower production thresholds, trigger live OTPs, repeat wrong codes, bypass Easy Auth, + or change production policy for a monitoring test. Use an approved non-phone test sign-in if needed. ### Build a Workbook when you need a shared view 1. Review the [Microsoft monitoring repository](https://github.com/microsoft/external-id-azure-monitor) - and its authentication, MFA, and user-behavior templates linked above. Inspect - **serializedData** query definitions with your query maintainer. These are ARM templates, - not text to paste wholesale into Logs. **Deploy to Azure** can create resources/delegation; - don't select it just to inspect a query. -2. Check tables, tenant features, personal fields, units, filters, and time settings. - The [risk template](https://github.com/microsoft/external-id-azure-monitor/blob/main/templates/riskDetectionWorkbookDeployment.json) - doesn't make risk features available in your tenant. -3. Open **Azure portal > Monitor > Workbooks > New**, enter **Edit**, then **Add > Add query**. - Select **Logs (Analytics)** and your workspace. Use the reviewed query and follow - [Workbook authoring](https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-create-workbook) - and [Add queries](https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-create-workbook#add-queries). - Choose a suitable **Visualization**, **Run Query**, then **Done editing**. -4. Label record/step counts clearly, explain missing data and arrival delay, and include - freshness. Bind app/workspace/time parameters into every relevant query and test changes; - selectors don't automatically replace hard-coded filters. Restrict detailed user/IP views. -5. **Save** to the agreed subscription/resource group/location and have a teammate check - access. Workbook sharing doesn't grant underlying log access, create alerts, or prove - they are enabled. + with the query maintainer. Its templates are deployable ARM resources, not text to paste into + Logs; inspecting them does not require selecting **Deploy to Azure**. +2. In **Monitor > Workbooks > New**, add the reviewed workspace query using Microsoft's + [Workbook authoring guide](https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-create-workbook). + Verify tables, licensing, filters, units, time range, freshness, and personal-data exposure. +3. Save to the approved location and have another authorized user test access. Workbook sharing + does not grant log access, create alerts, or enable tenant risk features. ### Investigate alerts and manage the data -1. In **Monitor > Alerts > selected alert**, check the rule, workspace, app/tenant scope, - UTC window, measured value, and rule/collection health. Open the saved query for that - period, then the relevant Entra **Basic info**, **Authentication Details**, and policy results. -2. Separate user mistakes/interruptions, releases, and policy changes from outages or abuse. - Escalate suspected compromise through your security response process; a heuristic isn't - a confirmed fraud verdict. -3. For phone concerns, compare [runtime-specific Function logs](MONITORING.md#runtime-specific-log-discovery) - and restricted provider records. `SigninLogs.CorrelationId`, Function `OperationId`, - request IDs, and provider references are **not guaranteed equal**. Prove an association - before joining them. No match doesn't prove no Function call; fallback can complete - authentication through another path. Provider acceptance isn't handset receipt. -4. Keep raw identity/device/IP/phone data in approved systems. Check workspace/table access, - Workbook sharing, and recipients. Don't put tokens, credentials, OTPs, or messages in - alerts/public issues. Query filters are **not an access-control boundary**. -5. Review **Workspace > Tables** plan, analytics retention, and total retention using - [retention instructions](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/data-retention-configure). - Confirm saved settings cover your investigation period; Entra portal and workspace - retention are separate. -6. Check **Usage and estimated costs** and **Cost Management > Cost analysis** with the - [cost guidance](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/cost-logs). - Export selects categories, not an app-only stream: query filters don't remove tenant-wide - ingestion charges. Include retention, queries, alert frequency/dimensions, notifications, - and separately enabled services. Provider billing is separate again. -7. Review caps/transformations with the collection maintainer; they can remove evidence - the rules need. Keep setup notes, test results, contacts for support/security/privacy/ - retention/costs, and escalation/maintenance arrangements together. Walk a teammate through - the query, Workbook, rule health, and test notification without sending OTPs or changing policy. +1. In **Monitor > Alerts**, verify the rule, workspace, tenant/app scope, UTC window, measured + value, and collection health. Re-run the saved query, then inspect the matching Entra details. +2. Separate user interruption and planned changes from outages or abuse. Escalate suspected + compromise through the security process; a heuristic is not a fraud verdict. +3. For phone concerns, compare [Function logs](MONITORING.md#runtime-specific-log-discovery) and + restricted provider records. Their correlation IDs are not guaranteed to match. Prove any + association; provider acceptance still is not handset receipt. +4. Keep identity, device, IP, and phone data in approved systems. Query filters are not access + control. Never put tokens, credentials, OTPs, messages, or raw personal data in alerts or issues. +5. Review [retention](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/data-retention-configure), + [log costs](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/cost-logs), caps, + transformations, alert frequency, and recipients with their owners. Keep the query, tests, + contacts, and escalation procedure together and walk a teammate through them. diff --git a/dotnet/README.md b/dotnet/README.md index 7004c97..4074ed2 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -55,17 +55,13 @@ Keep values as strings, including `EPP_PROVIDER_TIMEOUT_MS`, for example `"1500" Replace placeholders; credentials belong in the adapter's Key Vault secrets, not this file. A missing/invalid selected channel endpoint fails closed without fallback. -Core Tools loads `Values` into environment variables. [AppConfig.Read](Src/AppConfig.cs) reads them -through `IEnv`; direct worker execution and unit tests do not automatically load local settings. -Restart the host after edits. Configure local host storage other than Azurite separately; do not copy -the emulator connection into Azure. Core Tools does not resolve Key Vault references locally; supply -the local test PEM or base64 PEM directly. The [project](dotnet.csproj) excludes private local settings -from publish output. - -For Azure, configure the same application variables on the serving app/slot's **Environment variables -→ App settings** page and resolve the private PEM through a Key Vault reference. Key Vault provider -credentials use managed identity, not the developer's CLI login. Use loopback-only local evaluation -or the offline tests' injected environment and secret resolver for local development. +Core Tools loads `Values` into environment variables; direct worker execution and unit tests do not +load local settings. [AppConfig.Read](Src/AppConfig.cs) reads them through `IEnv`. Restart after +edits. Core Tools cannot resolve Key Vault references locally, so use only a local test PEM or +base64 PEM. The [project](dotnet.csproj) excludes local settings from publish output. + +In Azure, configure the same app settings and use a Key Vault reference for the private PEM. +Provider credentials use managed identity, not a developer CLI login. Keep local evaluation on loopback. ## Request behavior @@ -97,28 +93,16 @@ For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHT The host's OpenTelemetry setting does not by itself initialize a direct worker exporter; verify actual `ILogger` collection before depending on event-based alerts. -`SendOtp` validates and decrypts the request, selects the configured provider in its private -`SelectProvider` method, resolves credentials, and calls the provider's `SendOtpAsync`. -That method passes its request factory and typed response mapper to -`PhoneProviderBase.SendJsonAsync`, which builds and sends the `HttpRequestMessage`, -reads/deserializes the response, and returns a normalized `ProviderResult`. -`SendOtp` maps that result to the endpoint HTTP response. -The shared transport uses redirects disabled, `ResponseHeadersRead`, and a bounded timeout. -Each provider maps its recognized statuses to an outcome; selection uses `EPP_PROVIDER_NAME`. -Request-aware routing or multiple accounts require a separate design for trust, configuration, -credentials, and testing, not just a change to `SelectProvider`. - -`CredentialTokenService` is the hosted startup warmer and runtime credential cache. -`EPP_KEY_VAULT_CACHE_ENABLED` controls API-key caching; `EPP_ACCESS_TOKEN_CACHE_ENABLED` controls -OAuth caching. Both default to `true`. Setting the selected switch to `false` skips startup warmup -and fetches on every live request, using fresh SDK credentials for OAuth. -When enabled, it asks the selected provider for credentials at startup and on cache misses. -Each provider owns credential acquisition and its secret names. The service stores -the result in .NET `MemoryCache` until the credential's absolute expiry; the next request fetches a -replacement. There is no polling timer or separate cache implementation. The fetch has a -2.5-second cancellation budget linked to its caller's token. `MemoryCache.GetOrCreateAsync` -does not guarantee a single concurrent fetch on cache misses; do not assume the JavaScript/Python -shared-acquisition behavior applies here. Evaluation remains independent. +`SendOtp` validates and decrypts, selects `EPP_PROVIDER_NAME`, resolves credentials, and calls the +provider. `PhoneProviderBase.SendJsonAsync` owns bounded HTTP transport and typed response +mapping; each provider maps its statuses to `ProviderResult`. Request-aware routing or multiple +accounts require a separate trust, configuration, credential, and test design. + +`CredentialTokenService` warms and caches the selected provider credential. The shared cache +switches default to `true`; disabling the selected switch skips warmup and fetches on every live +request. Enabled credentials remain in `MemoryCache` until absolute expiry. There is no polling +timer, and concurrent misses can fetch more than once. The fetch has a 2.5-second cancellation +budget. See the [shared cache contract](../docs/CONTRACT.md#credential-caching-and-refresh). | Source | Purpose | |---|---| diff --git a/javascript/README.md b/javascript/README.md index 5841814..44ae27b 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -54,16 +54,13 @@ Keep `Values` as strings, including `EPP_PROVIDER_TIMEOUT_MS`, for example `"150 Replace placeholders; API keys belong in Key Vault, not this file. A missing/invalid selected channel endpoint fails closed without fallback. -Core Tools copies `Values` into the process environment; direct Node processes and the offline tests -do **not** automatically load this file. [AppConfig](src/functions/config.js) reads `process.env` -once per call to `readConfig()`. Restart the host after changing settings. Configure any local host -storage other than Azurite separately; do not copy a local emulator connection into Azure. Core Tools -does not resolve Key Vault references locally; supply the local test PEM or base64 PEM directly. +Core Tools copies `Values` into the process environment; direct Node processes and offline tests do +not load this file. [AppConfig](src/functions/config.js) reads `process.env` when `readConfig()` runs. +Restart after edits. Core Tools cannot resolve Key Vault references locally, so use only a local +test PEM or base64 PEM. Never copy Azurite settings into Azure. -For Azure, set these application variables on the Function App/slot's **Environment variables → App -settings** page and use a Key Vault reference for the private PEM. The provider-secret resolver uses -managed identity; signing into the CLI locally does not supply that identity. Local evaluation avoids -provider lookup, while tests inject mocked credentials and HTTP. Keep the local endpoint on loopback. +In Azure, configure the same app settings and use a Key Vault reference for the private PEM. +Provider secrets use managed identity, not a developer CLI login. Keep local evaluation on loopback. ## Request behavior @@ -94,13 +91,11 @@ The [cache switches and defaults](../docs/CONTRACT.md#credential-caching-and-ref Disabling the selected cache removes startup preparation, polling, and cross-request reuse, not live credential acquisition. -With caching enabled, the app-start hook selects `ApiKeyCache` or `AccessTokenCache` from the provider credential spec. -Only that cache starts: API keys use Key Vault and `lru-cache`; access tokens use the MI/Entra SDKs, -without Key Vault. One shared 30-second refresh loop and one in-flight acquisition keep warm reads -nonblocking. Configuration changes require restart; failures never extend expiry. A small HTTP-client -wrapper propagates cancellation to the installed identity SDK. Shutdown prevents late publication. See the -[refresh contract](../docs/CONTRACT.md#credential-caching-and-refresh). Leave the provider unset for -local evaluation-only use without credential acquisition; prewarming never sends an OTP. +With caching enabled, startup selects only the provider's API-key or OAuth cache. API keys use +Key Vault and `lru-cache`; OAuth uses the managed-identity/Entra SDKs. A shared 30-second refresh +loop and one in-flight acquisition keep warm reads nonblocking; failures never extend expiry. +See the [refresh contract](../docs/CONTRACT.md#credential-caching-and-refresh). Leave the provider +unset for local evaluation without credential acquisition; prewarming never sends an OTP. | Source | Purpose | |---|---| diff --git a/python/README.md b/python/README.md index 510c1e3..66f1930 100644 --- a/python/README.md +++ b/python/README.md @@ -53,17 +53,13 @@ Keep values as strings, including `EPP_PROVIDER_TIMEOUT_MS: "1500"`. Replace pla API keys belong in provider-named Key Vault secrets, not this file. A missing/invalid selected channel endpoint fails closed without fallback. -Core Tools loads `Values` into `os.environ`. Direct Python execution and pytest do not automatically -read local settings. [read_config](src/config.py) returns an `AppConfig` object; the handler/engine -use attributes such as `config.provider_name`, not dictionary key lookups. Restart the host after -settings change. Configure local host storage other than Azurite separately; do not copy the emulator -connection into Azure. Core Tools does not resolve Key Vault references locally; supply the local test -PEM or base64 PEM directly. - -For Azure, set the same application variables on the serving app/slot's **Environment variables → App -settings** page. Use a Key Vault reference for the private PEM. Provider secrets require managed -identity, which is not supplied by a developer's CLI login. Use local evaluation or the mocked offline -tests on an ordinary workstation, and bind local hosts only to loopback. +Core Tools loads `Values` into `os.environ`; direct Python execution and pytest do not. +[read_config](src/config.py) returns an `AppConfig`, and the handler reads its attributes. +Restart after edits. Core Tools cannot resolve Key Vault references locally, so use only a local +test PEM or base64 PEM. Never copy Azurite settings into Azure. + +In Azure, configure the same app settings and use a Key Vault reference for the private PEM. +Provider secrets use managed identity, not a developer CLI login. Keep local evaluation on loopback. ## Request behavior @@ -94,14 +90,11 @@ The [cache switches and defaults](../docs/CONTRACT.md#credential-caching-and-ref Disabling the selected cache removes startup preparation, polling, and cross-request reuse, not live credential acquisition. -With caching enabled, worker initialization selects `ApiKeyCache` or `AccessTokenCache` from the provider's credential specification. -Only the selected cache starts: API keys use Key Vault and `cachetools.TTLCache`; access tokens use -the MI/Entra SDKs without Key Vault. One daemon loop polls every 30 seconds. Configuration changes -require restart. Callers can stop waiting without abandoning shared reads; synchronous SDK I/O uses connect/read -timeouts, not a total transport deadline. `atexit` stops refresh and releases waiters; unfinished -daemon reads cannot publish or block process exit. See the -[refresh contract](../docs/CONTRACT.md#credential-caching-and-refresh). Leave the provider unset for -local evaluation without background credential acquisition. +With caching enabled, startup selects only the provider's API-key or OAuth cache. API keys use +Key Vault and `cachetools.TTLCache`; OAuth uses the managed-identity/Entra SDKs. A daemon polls +every 30 seconds; synchronous SDK calls use connect/read timeouts rather than one total deadline. +See the [refresh contract](../docs/CONTRACT.md#credential-caching-and-refresh). Leave the provider +unset for local evaluation without background credential acquisition. | Source | Purpose | |---|---| diff --git a/setup/docs/README.md b/setup/docs/README.md index c64b948..a39de8b 100644 --- a/setup/docs/README.md +++ b/setup/docs/README.md @@ -112,22 +112,19 @@ After Step 2 approval, setup configures: ## Prerequisites for Step 2 -- **Azure Cloud Shell in PowerShell mode, or PowerShell 7+** with an Azure CLI user login. - Certificate issuance happens inside Key Vault without local certificate-store cmdlets. -- Azure CLI **2.60.0+ for FC1** or **2.48.1+ for EP1** on `PATH`, with access to GitHub, Azure, Microsoft Graph, and Key Vault. - Setup installs the Azure CLI Bicep component after confirmation when it is missing. Azure CLI itself - must be installed before running the script. FC1 publication and EP1 Python builds also need - network access to SCM. +- **Azure Cloud Shell in PowerShell mode, or PowerShell 7+**, with an Azure CLI user login. + Certificate issuance stays in Key Vault; no local certificate-store cmdlets are needed. +- Azure CLI **2.60.0+ for FC1** or **2.48.1+ for EP1** on `PATH`, with GitHub, Azure, + Microsoft Graph, Key Vault, and required SCM access. Setup can install Bicep after confirmation, + but Azure CLI must already be installed. - An Azure **user** account permitted to deploy at subscription scope, create the listed resources, and create the scoped Azure role assignments. Bicep grants the operator **Key Vault Certificates Officer** for issuance and **Key Vault Secrets Officer** for provider credentials, scoped to this deployment's vault. -- **Certificate reuse:** the operator needs ARM read access to the source vault, certificate-read - access, and secret-version metadata-read access before approval. For a different vault, the - approved plan also requires permission to grant the Function identity secret-read access: - role-assignment write for RBAC vaults, or access-policy write for access-policy vaults. - The source vault must be in the customer tenant; `-KeyVaultSubscriptionId` identifies a - different source subscription. Setup never fetches its private-key value locally. +- **Certificate reuse:** the operator needs source-vault ARM read, certificate read, and + secret-version metadata read. A different vault also requires permission to grant the Function + secret read through RBAC or an access policy. The vault must be in the customer tenant; + use `-KeyVaultSubscriptionId` for another subscription. Setup never reads the private key locally. - A Microsoft Entra **Privileged Role Administrator** for granting the Microsoft first-party service principal Graph `Application.Read.All`. Microsoft Graph operations use the current Azure CLI user session through `az ad` and `az rest`; Azure subscription roles alone do not grant these @@ -135,19 +132,15 @@ After Step 2 approval, setup configures: - Microsoft Graph **beta** access for the Entra `signInAudienceRestrictions` allowed-tenants preview. The selected provider tenant is allowed in addition to the app's home tenant, which Entra always allows. See the [allowed-tenants schema](https://learn.microsoft.com/en-us/graph/api/resources/allowedtenantsaudience?view=graph-rest-beta). -- The selected **Linux Flex Consumption FC1 or Premium EP1** plan available in the chosen region, - with sufficient subscription quota. Availability is checked for that plan only; regional service - availability and resource-provider registration do not guarantee quota. If deployment reports - `SubscriptionIsOverQuotaForSku`, resolve the - [regional quota issue](Troubleshooting.md#deployment-fails-with-subscriptionisoverquotaforsku) - before retrying. Setup registers missing required Azure - resource providers automatically after the single approval. The Azure account needs the - providers' subscription-scoped `/register/action` permission (included in Contributor/Owner). -- **.NET selection only:** install the .NET 8 SDK and allow NuGet access. Setup runs `dotnet publish` - automatically for `linux-x64`, packages the publish output, and deploys it. No manual build step - or upload is required. JavaScript and Python do not require this SDK. -- **Python selection:** Azure performs the Linux dependency build. No local Python, pip, or Windows - dependency installation is needed. The source archive is never used directly as run-from-package. +- The selected **Linux FC1 or EP1** plan available in the region, with sufficient subscription + quota. Service availability and provider registration do not guarantee quota. Resolve + [`SubscriptionIsOverQuotaForSku`](Troubleshooting.md#deployment-fails-with-subscriptionisoverquotaforsku) + before retrying. After approval, setup registers required providers; the operator needs their + subscription-scoped `/register/action` permission. +- **.NET only:** install the .NET 8 SDK and allow NuGet access. Setup publishes for `linux-x64`, + packages, and deploys automatically. +- **Python:** Azure builds Linux dependencies; no local Python/pip install is required. The source + archive is not a run-from-package artifact. ### Permissions are separate diff --git a/setup/docs/Troubleshooting.md b/setup/docs/Troubleshooting.md index 76f8ba4..4b09ea9 100644 --- a/setup/docs/Troubleshooting.md +++ b/setup/docs/Troubleshooting.md @@ -46,14 +46,12 @@ Evaluation success, provider acceptance, and recipient delivery are different ev ## Recover without destructive cleanup 1. **Locate the failure.** Before approval, checks do not mutate Azure resources, although sign-in - or Bicep installation may have occurred. After approval, partial Azure/Microsoft Entra ID changes - can remain without a summary. Inspect **Azure portal > subscription/resource group > Deployments** - and the original error. + or Bicep installation may occur. After approval, partial Azure or Microsoft Entra ID changes can + remain without a summary. Inspect the original error and **resource group > Deployments**. 2. **Fix, then rerun with the same reviewed inputs:** tenant, subscription, app, runtime, plan, - prefix, provider/route, and source/package versions. Changed IDs can create resources or break - key continuity; a changed route can reconfigure the endpoint. There is no checkpoint resume or - automatic undo. Active endpoints need a change window because reruns can disable ingress and - change settings. + prefix, provider/route, and source/package versions. Changed IDs can create resources, break key + continuity, or reconfigure the endpoint. There is no checkpoint resume or automatic undo; use a + change window for active endpoints. 3. **Verify the failed stage and complete acceptance checks.** Never use resource-group deletion, vault purge/recreation, encryption-credential removal, or shared-grant revocation as a retry. Retirement uses the separate [rollback procedure](README.md#rollback-and-decommissioning). @@ -88,14 +86,10 @@ requests can still use a retired key. The single-region setup script does not provision Front Door or a multi-region readiness endpoint. Use the [manual Front Door guide](../../docs/FRONTDOOR.md) for that option. -- A probe returning 401/403 or 404 is not healthy. Verify the dedicated readiness handler, its exact - path, the origin host header, and the profile-pinned network restrictions. Do not exempt SendOtp - or disable Easy Auth to make probes pass. -- An authenticated SendOtp request returning 403 during an outage still failed. Check origin state, - access restrictions, and authentication diagnostics; do not assume every 403 is a normal failover - transition or that the caller retries it. -- A successful request through the shared URL does not prove every origin works. Correlate safe - request identifiers with regional telemetry and verify each origin after recovery. +- A 401/403/404 probe is unhealthy. Check the readiness path, origin host header, and profile-pinned + network restriction. Never exempt SendOtp or disable Easy Auth. +- An authenticated SendOtp 403 is a failure, not proof of failover. Check origin, ingress, and auth. +- A shared-URL success does not prove every origin works; verify each with safe regional telemetry. - Distinguish delayed aggregate metrics from event timestamps and client observations. See the [recorded test results and limitations](../../docs/FRONTDOOR.md#observed-failover-results-and-limitations). @@ -104,16 +98,13 @@ Use the [manual Front Door guide](../../docs/FRONTDOOR.md) for that option. **Check:** the selected subscription's regional **EP1 VMs** quota, not authentication or resource registration. A supported Linux EP1 region can still have zero quota for your subscription. -1. Confirm the tenant, subscription, region, and SKU in the deployment plan and Azure error. -2. In the Azure portal, open **Quotas**, select **App Service**, and filter to the intended - subscription and region. Review **EP1 VMs**, its current usage, and the requested deployment's - requirements. This is an App Service quota, not a general-purpose Compute VM quota. -3. Request an increase sufficient for the deployment and any planned scaling. Requesting an - increase does not mean it is approved; verify the effective limit after approval. -4. If the quota is not adjustable in the portal or the request is rejected, create an Azure - support request under **Service and subscription limits (quotas)** for - **Function or Web App (Windows and Linux)**. Include the region, Linux deployment type, EP1 - SKU, current limit, requested limit, and the error's tracking ID. +1. Confirm the tenant, subscription, region, and SKU in the plan and Azure error. +2. In **Quotas > App Service**, filter to that subscription/region and inspect **EP1 VMs** usage + and limit. This is not a general Compute VM quota. +3. Request enough quota for deployment and scaling, then verify the effective approved limit. +4. If the portal cannot adjust it, open a **Service and subscription limits (quotas)** request for + **Function or Web App (Windows and Linux)**. Include region, Linux/EP1, current/requested limits, + and the tracking ID. 5. Alternatively, choose another region only after checking its quota, service availability, and your residency and provider requirements. Review the updated deployment plan before approval. From 6c2c81cf3355c52ae4e4f243107e8ce28c19a155 Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 14:05:33 -0700 Subject: [PATCH 07/13] docs: frame regional resiliency positively Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 4 ++-- TECHNICAL.md | 26 +++++++++++++++++--------- docs/FRONTDOOR.md | 37 ++++++++++++++++++------------------- docs/README.md | 3 ++- dotnet/README.md | 5 +++-- javascript/README.md | 5 +++-- python/README.md | 5 +++-- 7 files changed, 48 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 63aa9be..bc2d0f3 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,8 @@ See the [sample's production limitations](docs/CONTRACT.md#production-limitation - [Multi-region routing with Azure Front Door](docs/FRONTDOOR.md) - [Conservative primary-to-secondary provider fallback](docs/MULTI-PROVIDER-IMPLEMENTATION.md) -Guided setup enables neither optional design. Each requires separate implementation, capacity -planning, validation, operations, and rollback. +Start with the validated single-region endpoint, then implement either design through its +capacity, validation, operations, and rollback guidance. ## Getting help diff --git a/TECHNICAL.md b/TECHNICAL.md index 39a7bbf..68e9160 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -6,11 +6,8 @@ Use this page for implementation choices, settings, and packages. Jump to [implementations](#implementations), [guided setup](#guided-epp-setup), [download](#download-a-function-zip), [build](#build-zips-locally), [settings](#configure-environment-variables), [provider details](#provider-specific-integration-details), -or [security](#security). - -Azure Front Door is an [optional manual multi-region design](docs/FRONTDOOR.md), not a feature -enabled by the single-region setup script. Its additional readiness endpoint is not shipped in -the release packages, and regional failover does not change the SendOtp contract. +or [security](#security). For regional continuity, see +[resiliency and business continuity](#resiliency-and-business-continuity). ## Single-region architecture @@ -32,6 +29,17 @@ without submitting a message to the provider. When caching is enabled, credentia can run independently; it never sends an OTP. Authentication failures can return **401 or 403**, neither of which is a successful evaluation. +## Resiliency and business continuity + +Start with a validated single-region deployment. When regional continuity is required, the +[Azure Front Door design](docs/FRONTDOOR.md) describes one public URL across independently +provisioned regional origins while preserving the SendOtp contract. + +The guide covers survivor capacity, shared encryption-key coordination, regional dependencies, +safe failover drills, and rollback. It also defines a dedicated non-delivering readiness endpoint +to implement and validate with each regional package. Build regional provisioning and readiness +into your approved BCDR process, then activate only after both directions pass the documented checks. + ## Implementations | Language | Status | Folder | @@ -46,8 +54,8 @@ but provider APIs, binding errors, credential caching, and telemetry can differ For [local development or manual deployment](docs/ONBOARDING.md#optional-local-development-and-manual-deployment), follow the selected runtime guide. For a custom service integration, start with the -[provider-neutral BYOT interface](docs/BRING-YOUR-OWN-PROVIDER.md). Your team owns the outbound -integration; that guide adds neither an adapter nor guided setup support. +[provider-neutral BYOT interface](docs/BRING-YOUR-OWN-PROVIDER.md), then implement and validate +the outbound adapter through the selected runtime. ## Guided EPP setup @@ -69,8 +77,8 @@ One deployment accepts SMS and voice; each request's `channel` selects its confi **Optional developer/manual deployment path:** guided setup already selects, verifies, builds as needed, and publishes the package. Customers using it should continue with -[provider authentication and validation](docs/ONBOARDING.md#complete-provider-authentication), -not deploy a second ZIP. +[provider authentication and validation](docs/ONBOARDING.md#complete-provider-authentication); +the deployed package is ready for that handoff. Download the latest successful CI ZIP for your chosen language: diff --git a/docs/FRONTDOOR.md b/docs/FRONTDOOR.md index 65d6701..8a76f5e 100644 --- a/docs/FRONTDOOR.md +++ b/docs/FRONTDOOR.md @@ -3,11 +3,9 @@ [Documentation](README.md) | [Single-region setup](GETTING-STARTED.md) | [Routing troubleshooting](../setup/docs/Troubleshooting.md#optional-front-door-deployments) Use Azure Front Door when you want **one public EPP URL backed by Function Apps in multiple regions**. -You configure this option manually. For a single region, start with the -[five-step setup guide](GETTING-STARTED.md). - -**No Front Door deployment script is supplied.** This manual design is not guided setup, -and failover can interrupt requests. +Start with a validated [single-region deployment](GETTING-STARTED.md), then use this guide to +plan and implement the regional origins, readiness route, Front Door configuration, validation, +failover drills, and rollback. Treat transition errors as part of the acceptance criteria. Follow [scope](#scope-and-observed-behavior), [architecture](#architecture-one-url-multiple-origins), [plan/capacity](#1-plan-the-regional-deployment), [origins/keys](#2-prepare-equivalent-independently-provisioned-origins), @@ -22,25 +20,26 @@ Testing used isolated JavaScript origins, Front Door Standard, two/three regions and a separate non-delivering readiness handler. It covered encrypted evaluation, invalid callers, direct-origin restrictions, and individual Function stop/restart. -**The readiness handler is not released.** Implement and validate the -[readiness contract](#3-provide-a-non-delivering-readiness-endpoint) before enabling probes; -no setting adds it to a ZIP. .NET/Python adaptations are untested. +Implement the [readiness contract](#3-provide-a-non-delivering-readiness-endpoint) as part of each +regional package and validate it before enabling probes. The released packages remain the +single-region baseline; include runtime-specific validation for .NET or Python. -We **did not test** live SMS/voice delivery, calls and retries from the real Entra service, a full -Azure regional outage, custom domains, WAF, Private Link, or production load. Test the features your -deployment needs before activating policy. +The recorded trials cover encrypted evaluation, caller and origin restrictions, and controlled +Function stop/restart. Extend your acceptance plan to the features you will use: approved live +SMS/voice checks, real Microsoft Entra caller behavior, regional dependency loss, custom domains, +WAF or Private Link, and production-representative load. ### Regional resiliency responsibility -Microsoft owns defining, configuring, documenting, and validating its standard regional-failover -baseline and exercise procedure; customers using it should not design regional DR themselves. -Customers operate the deployed baseline: ownership, capacity, credentials/keys, monitoring, and -approved drills. Replacing the topology makes its alternate design/validation customer-owned; -operating the standard baseline does not transfer its design responsibility. +Microsoft defines, configures, documents, and validates its standard regional-failover baseline +and exercise procedure. Customers operate that baseline: ownership, capacity, credentials and +keys, monitoring, and approved drills. An alternate topology becomes a customer-owned design +with its own validation plan. -Onboarding remains single-region and Front Door manual. Readiness, dependency isolation, capacity, -real-caller behavior, and regional-outage validation remain gaps. Deployment owners must approve -RTO/RPO, tolerable failures, latency, and required evidence; historical results do not set those targets. +Use single-region onboarding as the foundation, then add Front Door, readiness, dependency +isolation, survivor capacity, real-caller checks, and regional-outage evidence. Deployment owners +set RTO/RPO, tolerable failures, latency, and evidence requirements; historical results provide +test context rather than those targets. ## Architecture: one URL, multiple origins diff --git a/docs/README.md b/docs/README.md index 1166cfa..a9ccac8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,7 +51,8 @@ nonproduction environment; deployment does not activate policy. ## Optional designs -These designs require separate implementation and validation; guided setup enables neither. +Build these designs from a validated single-region endpoint and follow their dedicated +implementation, validation, operations, and rollback guidance. - **Route across regions:** [Multi-region Front Door design](FRONTDOOR.md) - **Add provider fallback:** [Multi-provider implementation](MULTI-PROVIDER-IMPLEMENTATION.md) diff --git a/dotnet/README.md b/dotnet/README.md index 4074ed2..b455b7a 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -29,8 +29,9 @@ not a local host or second deployment. Run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint); offline tests do not verify platform authentication. -**Optional:** [Front Door](../docs/FRONTDOOR.md) has no supplied deployment script/readiness handler. -Trials used JavaScript; independently implement and validate .NET readiness and regional deployment. +**Regional continuity:** use the [Front Door guide](../docs/FRONTDOOR.md) to implement and validate +the readiness route and regional deployment for .NET. The recorded trials used JavaScript, so +include .NET-specific package, startup, dependency, and telemetry checks. ## Environment configuration diff --git a/javascript/README.md b/javascript/README.md index 44ae27b..a995ef5 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -27,8 +27,9 @@ is narrower than the bundled adapter set. Then run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint); offline tests do not verify platform authentication. -**Optional:** [Front Door](../docs/FRONTDOOR.md) uses multiple regional origins behind one URL. -The JavaScript trials' readiness handler is not released, and no Front Door deployment script is supplied. +**Regional continuity:** [Front Door](../docs/FRONTDOOR.md) uses multiple regional origins behind +one URL. Its JavaScript trials and readiness contract provide the starting point for implementing +and validating your regional package and provisioning process. ## Environment configuration diff --git a/python/README.md b/python/README.md index 66f1930..207d87f 100644 --- a/python/README.md +++ b/python/README.md @@ -27,8 +27,9 @@ Continue with [provider authentication and validation](../docs/ONBOARDING.md#com Run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint); offline tests do not verify platform authentication. -**Optional:** [Front Door](../docs/FRONTDOOR.md) has no supplied deployment script/readiness handler. -Trials used JavaScript; independently implement and validate Python readiness and regional deployment. +**Regional continuity:** use the [Front Door guide](../docs/FRONTDOOR.md) to implement and validate +the readiness route and regional deployment for Python. The recorded trials used JavaScript, so +include Python-specific package, startup, dependency, and telemetry checks. ## Environment configuration From 1d992643b4cf3b01e9a5313653ba01d37b69582b Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 14:13:50 -0700 Subject: [PATCH 08/13] docs: make guidance more conversational Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 5 +++-- TECHNICAL.md | 4 ++-- docs/APPLICATION-INSIGHTS.md | 15 ++++++++------- docs/CONTRACT.md | 14 +++++++------- docs/GETTING-STARTED.md | 3 ++- docs/LOGGING.md | 4 ++-- docs/MONITORING.md | 3 ++- docs/MULTI-PROVIDER-IMPLEMENTATION.md | 8 ++++---- docs/ONBOARDING.md | 6 +++--- docs/README.md | 6 +++--- docs/SIGN-IN-MONITORING.md | 10 +++++----- dotnet/README.md | 8 ++++---- javascript/README.md | 7 ++++--- python/README.md | 7 ++++--- setup/docs/README.md | 9 ++++----- setup/docs/Troubleshooting.md | 6 +++--- 16 files changed, 60 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index bc2d0f3..ea12587 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ Connect Microsoft Entra ID to your phone provider for SMS and voice one-time pas Deploy **one endpoint in one Azure region**, using JavaScript, C#, or Python. Guided setup handles Azure resources and code publication; no repository clone is needed. -**Start in a dedicated nonproduction tenant and subscription.** This sample is not production -certification. Review [requirements](#requirements) before purchasing or deploying. +**Start in a dedicated nonproduction tenant and subscription.** This sample demonstrates the +integration pattern; it is not a production certification. Review [requirements](#requirements) +before purchasing or deploying. ## Table of Contents diff --git a/TECHNICAL.md b/TECHNICAL.md index 68e9160..83e0cf1 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -2,8 +2,8 @@ [Documentation](docs/README.md) | [First deployment](docs/GETTING-STARTED.md) | [HTTP contract](docs/CONTRACT.md) -Use this page for implementation choices, settings, and packages. -Jump to [implementations](#implementations), [guided setup](#guided-epp-setup), +Use this page when you need implementation details, app settings, or package guidance. +Go to [implementations](#implementations), [guided setup](#guided-epp-setup), [download](#download-a-function-zip), [build](#build-zips-locally), [settings](#configure-environment-variables), [provider details](#provider-specific-integration-details), or [security](#security). For regional continuity, see diff --git a/docs/APPLICATION-INSIGHTS.md b/docs/APPLICATION-INSIGHTS.md index d1f1c56..31f9097 100644 --- a/docs/APPLICATION-INSIGHTS.md +++ b/docs/APPLICATION-INSIGHTS.md @@ -2,18 +2,19 @@ [Documentation](README.md) | [Monitoring walkthrough](MONITORING.md) | [Log schema](LOGGING.md) -**Find logs and create alerts:** use the [monitoring walkthrough](MONITORING.md). -**Check collection or missing data:** start with [single-region configuration](#single-region-configuration) -below. This reference covers JavaScript, .NET isolated, and Python; Front Door is not required. +Looking for logs or alerts? Start with the [monitoring walkthrough](MONITORING.md). +If telemetry is missing or incomplete, start with +[single-region configuration](#single-region-configuration). This page covers JavaScript, +.NET isolated, and Python. -Function telemetry describes execution, timing, and logs, **not recipient delivery**. +Function telemetry shows execution, timing, and logs, **not recipient delivery**. [Microsoft Entra ID sign-in monitoring](SIGN-IN-MONITORING.md) uses a separate tenant export pipeline and does not prove delivery either. -These instructions do not deploy resources or enable instrumentation, alerts, or availability tests. -Use the [setup guide](GETTING-STARTED.md) and [approved test handoff](ONBOARDING.md#validate-the-deployed-endpoint). +This page assumes the endpoint is already deployed. Use the [setup guide](GETTING-STARTED.md) +first, then follow the [approved test handoff](ONBOARDING.md#validate-the-deployed-endpoint). -Jump to [collection pipelines](#how-telemetry-reaches-application-insights), +On this page: [collection pipelines](#how-telemetry-reaches-application-insights), [signals and tables](#signals-tables-and-metrics-are-different-things), [single-region checks](#single-region-configuration), [ingestion identity](#identity-and-ingestion), [multi-region layout](#multi-region-configuration), [coverage queries](#read-only-queries-to-establish-what-is-present), diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md index 673d570..0906848 100644 --- a/docs/CONTRACT.md +++ b/docs/CONTRACT.md @@ -2,10 +2,10 @@ [Documentation](README.md) | [Implementation walkthrough](BRING-YOUR-OWN-PROVIDER.md) | [Settings and packages](../TECHNICAL.md) -This defines the shared contract for [JavaScript](../javascript/), [Python](../python/) and +This page defines the shared contract for [JavaScript](../javascript/), [Python](../python/) and [.NET](../dotnet/). See [production limitations](#production-limitations) before production use. -Jump to [request/response](#1-http-api), [HTTP outcomes](#2-outcome--http-status-mapping), +Go to [request/response](#1-http-api), [HTTP outcomes](#2-outcome--http-status-mapping), [provider contract](#3-provider-contract), [settings](#4-configuration-app-settings--env), [caching](#credential-caching-and-refresh), [required behaviors](#5-required-behaviors), [logs](#application-logs), or [tests](#6-lightweight-tests). @@ -14,14 +14,14 @@ Jump to [request/response](#1-http-api), [HTTP outcomes](#2-outcome--http-status > request and delivery models are `Envelope`, `DeliveryContext` and provider-neutral OTP delivery. > Documentation names do not change the external JSON fields or `microsoft.mfa.otpDeliver.v1` version. -The design selects one provider per deployment. The HTTP Function owns the request lifecycle, while +Each deployment selects one provider. The HTTP Function owns the request lifecycle, while API-specific credentials, paths, headers, payloads, response rules, outcomes and safe failure classifications remain inside each provider implementation. -An [optional manual Front Door topology](FRONTDOOR.md) can route this same API to regional origins. -It does not change caller authentication, JWE validation, nonce semantics, or provider selection, -and it does not add automatic provider failover or delivery deduplication. Any readiness endpoint -is separate from this contract and must not deliver OTPs. +For regional continuity, the [Front Door design](FRONTDOOR.md) routes this same API to regional +origins while preserving caller authentication, JWE validation, nonce semantics, and provider +selection. Its readiness endpoint is separate from this contract and never delivers OTPs. +Provider fallback and delivery deduplication remain separate designs. --- diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index c80ad5a..f42f991 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -3,7 +3,8 @@ [Documentation](README.md) | [Setup reference](../setup/docs/README.md) | [Troubleshooting](../setup/docs/Troubleshooting.md) Deploy **one endpoint in one Azure region** for Microsoft Entra ID SMS and voice requests. -No clone, manual package build, or second language deployment is needed. +Guided setup downloads and publishes the selected package, so you can start without cloning or +building the repository. SMS and voice can use different provider URLs on the same endpoint; validate each intended channel. Follow in order: [prepare](#1-get-ready), [register](#2-register-your-application), diff --git a/docs/LOGGING.md b/docs/LOGGING.md index b7cf36a..72ab1bb 100644 --- a/docs/LOGGING.md +++ b/docs/LOGGING.md @@ -6,8 +6,8 @@ Use this reference when implementing or interpreting endpoint events. For an inc the monitoring walkthrough, not a search for a universal JSON request summary. **Never log request bodies, decrypted fields, credentials, nonces, raw exceptions, or provider -response bodies.** Keep SDK, platform, and proxy body tracing off. This page specifies emission, -not guaranteed collection. +response bodies.** Keep SDK, platform, and proxy body tracing off. This page describes what the +application emits; your monitoring pipeline determines what is collected. ## Runtime formats diff --git a/docs/MONITORING.md b/docs/MONITORING.md index 938c4da..b53a543 100644 --- a/docs/MONITORING.md +++ b/docs/MONITORING.md @@ -3,7 +3,8 @@ [Documentation](README.md) | [Collection diagnostics](APPLICATION-INSIGHTS.md) | [Log schema](LOGGING.md) | [Endpoint troubleshooting](../setup/docs/Troubleshooting.md#after-deployment-triage) Use this guide to find your endpoint's logs and set up alerts. Guided setup creates Application -Insights and a Log Analytics workspace, but **you still need to configure and test notifications**. +Insights and a Log Analytics workspace; your operations team chooses, configures, and tests the +notifications. For **tenant authentication and potential abuse signals**, use [Microsoft Entra ID sign-in monitoring](SIGN-IN-MONITORING.md). Endpoint setup does not configure that pipeline; diff --git a/docs/MULTI-PROVIDER-IMPLEMENTATION.md b/docs/MULTI-PROVIDER-IMPLEMENTATION.md index e1241b2..49d38ec 100644 --- a/docs/MULTI-PROVIDER-IMPLEMENTATION.md +++ b/docs/MULTI-PROVIDER-IMPLEMENTATION.md @@ -2,10 +2,10 @@ [Documentation](README.md) | [Normal deployment](GETTING-STARTED.md) | [Provider interface](BRING-YOUR-OWN-PROVIDER.md) -**The sample has no provider fallback.** This walkthrough requires customer code, durable state, -provider-specific approval, and separate testing. Keep one `/api/SendOtp` URL for Microsoft Entra ID -and a server-owned primary/secondary order for the same SMS channel, not caller-selected accounts -or a vendor ranking. +Use this guide when you need a customer-owned primary-to-secondary provider design. It adds code, +durable state, provider-specific approval, and dedicated testing around the existing +`/api/SendOtp` URL. Keep the provider order on the server for one SMS channel rather than letting +the caller select accounts or treating the order as a vendor ranking. Try a secondary **only with proof that the primary did not accept**. Timeouts, lost responses, and generic errors are not that proof; never automatically resend on those signals. diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index c12f805..738ab16 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -2,9 +2,9 @@ [Documentation](README.md) | [First deployment](GETTING-STARTED.md) | [Troubleshooting](../setup/docs/Troubleshooting.md) -**After guided setup:** complete [provider authentication](#complete-provider-authentication), -then [endpoint validation](#validate-the-deployed-endpoint). Azure and code are already deployed; -no local settings, Core Tools, or second deployment is needed. +Guided setup has already deployed Azure resources and code. Next, complete +[provider authentication](#complete-provider-authentication), then +[validate the endpoint](#validate-the-deployed-endpoint). | Task | Go to | | --- | --- | diff --git a/docs/README.md b/docs/README.md index a9ccac8..47d7e46 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,9 +2,9 @@ [Sample overview and architecture](../README.md) -**New deployment? Start with [Get started](GETTING-STARTED.md).** Confirm supported access and -Microsoft's approved test/activation procedure before spending or deploying. Use a dedicated -nonproduction environment; deployment does not activate policy. +**New deployment? Start with [Get started](GETTING-STARTED.md).** Before spending or deploying, +confirm supported access and Microsoft's approved testing and activation process. Use a dedicated +nonproduction environment. You will activate policy separately after validation. ## Table of Contents diff --git a/docs/SIGN-IN-MONITORING.md b/docs/SIGN-IN-MONITORING.md index 051ea18..1396829 100644 --- a/docs/SIGN-IN-MONITORING.md +++ b/docs/SIGN-IN-MONITORING.md @@ -9,12 +9,12 @@ | Diagnose missing records or quiet alerts | [Check data](#4-check-your-data-and-choose-a-query), [monitor ingestion](#monitor-ingestion-separately) | | Adapt a query or add advanced alerts | [Optional reference](#optional-reference) | -The six-step walkthrough creates one user sign-in alert. Use its baseline and measurement only -for that monitor; the other recommendations need their own scoped queries. +The six-step walkthrough builds one user sign-in alert. Treat it as a worked example: each +additional monitor needs its own scope, baseline, and query. -**Scope: Entra authentication and configuration, not Function or phone delivery.** -Keep identity details private. EPP setup does not export these logs or create alerts; -the steps below are actions for your administrators, not changes made by this guide. +This guide covers Microsoft Entra authentication and configuration. Function execution and phone +delivery have their own monitoring paths. Keep identity details private; your administrators +configure this export and its alerts after endpoint setup. ## Recommended monitors diff --git a/dotnet/README.md b/dotnet/README.md index b455b7a..62f9214 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -8,10 +8,10 @@ selected provider per deployment. Target: .NET 8 isolated worker, Azure Function ## Setup and deployment **Deploying the sample?** Use the [five-step guide](../docs/GETTING-STARTED.md). -Guided setup builds with the .NET 8 SDK and publishes. Continue with -[provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication), -not a local host or second deployment. -[Guided provider support](../docs/ONBOARDING.md#provider-credential-names) is narrower than the adapter set. +Guided setup builds with the .NET 8 SDK and publishes the Function. Next, complete +[provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). +The [guided provider list](../docs/ONBOARDING.md#provider-credential-names) covers a subset of +the adapters in this folder. **Developing a custom package?** Follow these steps from `dotnet/`: diff --git a/javascript/README.md b/javascript/README.md index a995ef5..8ce7b55 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -8,9 +8,10 @@ One configured provider owns its credentials, request mapping, and response inte ## Setup **Deploying the sample?** Use the [five-step guide](../docs/GETTING-STARTED.md). -After guided setup, go to [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication), -not local settings or another deployment. [Guided provider support](../docs/ONBOARDING.md#provider-credential-names) -is narrower than the bundled adapter set. +After guided setup, continue with +[provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). +The [guided provider list](../docs/ONBOARDING.md#provider-credential-names) covers a subset of +the adapters in this folder. **Developing a custom package?** Follow these steps from `javascript/`: diff --git a/python/README.md b/python/README.md index 207d87f..44b5456 100644 --- a/python/README.md +++ b/python/README.md @@ -8,9 +8,10 @@ selected provider per deployment. Target: Python 3.11, Azure Functions v4, Pytho ## Setup and deployment **Deploying the sample?** Use the [five-step guide](../docs/GETTING-STARTED.md). -Guided setup builds remotely and publishes; no local Python installation or second deployment. -Continue with [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). -[Guided provider support](../docs/ONBOARDING.md#provider-credential-names) is narrower than the adapter set. +Guided setup builds remotely and publishes the Function without requiring a local Python install. +Next, complete [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). +The [guided provider list](../docs/ONBOARDING.md#provider-credential-names) covers a subset of +the adapters in this folder. **Developing a custom package?** Follow these steps from `python/`: diff --git a/setup/docs/README.md b/setup/docs/README.md index a39de8b..90789fb 100644 --- a/setup/docs/README.md +++ b/setup/docs/README.md @@ -2,8 +2,8 @@ [Documentation](../../docs/README.md) | [First deployment](../../docs/GETTING-STARTED.md) | [Troubleshooting](Troubleshooting.md) -Use this reference for setup choices and maintenance. For a first deployment, follow the -five-step guide above; no repository clone or manual Bicep download is needed. +Use this page when you need details about setup choices, reruns, or maintenance. For a first +deployment, follow the five-step guide above; it handles the required source and Bicep files. | Task | Section | | --- | --- | @@ -18,9 +18,8 @@ five-step guide above; no repository clone or manual Bicep download is needed. Confirm [provider access and the approved Microsoft procedure](../../docs/ONBOARDING.md#before-purchasing-or-deploying) before purchasing or deploying. A provider purchase alone does not enable the tenant feature or authorize its API. -Setup creates **one endpoint in one Azure region**. For multiple regions, use the separate -[manual Front Door guide](../../docs/FRONTDOOR.md). Running setup again in another region does -not coordinate encryption keys or Front Door origins. +Setup creates **one endpoint in one Azure region**. When you are ready to add regional continuity, +follow the [Front Door guide](../../docs/FRONTDOOR.md) to coordinate origins and encryption keys. ## Availability diff --git a/setup/docs/Troubleshooting.md b/setup/docs/Troubleshooting.md index 4b09ea9..fb3d659 100644 --- a/setup/docs/Troubleshooting.md +++ b/setup/docs/Troubleshooting.md @@ -2,9 +2,9 @@ [Documentation](../../docs/README.md) | [Setup reference](README.md) | [Resume onboarding](../../docs/GETTING-STARTED.md) -Choose the failing stage below. **Do not delete resources or blindly retry a live send.** -Before rerunning setup, read [safe recovery](#recover-without-destructive-cleanup). -A completed deployment does not prove provider access or delivery. +Start with the stage that failed. Keep existing resources in place, and never blindly retry a +live send. Before rerunning setup, read [safe recovery](#recover-without-destructive-cleanup). +After deployment, complete the provider and delivery checks separately. ## Find your symptom From 60e6edcdaf314668d243f019ec16d89bafb89c5b Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 14:24:07 -0700 Subject: [PATCH 09/13] docs: frame guidance around capabilities Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 32 ++++++++++++--------- docs/APPLICATION-INSIGHTS.md | 9 +++--- docs/BRING-YOUR-OWN-PROVIDER.md | 9 +++--- docs/CONTRACT.md | 50 ++++++++++++++++++--------------- docs/FRONTDOOR.md | 23 ++++++++------- docs/GETTING-STARTED.md | 36 +++++++++++++----------- docs/MONITORING.md | 13 +++++---- docs/ONBOARDING.md | 11 ++++---- docs/README.md | 17 +++++------ docs/SIGN-IN-MONITORING.md | 9 +++--- dotnet/README.md | 4 +-- javascript/README.md | 4 +-- python/README.md | 4 +-- setup/docs/README.md | 5 ++-- setup/docs/Troubleshooting.md | 7 +++-- 15 files changed, 128 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index ea12587..d298aae 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ Connect Microsoft Entra ID to your phone provider for SMS and voice one-time passwords. Deploy **one endpoint in one Azure region**, using JavaScript, C#, or Python. -Guided setup handles Azure resources and code publication; no repository clone is needed. +Guided setup handles the Azure resources and publishes your selected runtime package. -**Start in a dedicated nonproduction tenant and subscription.** This sample demonstrates the -integration pattern; it is not a production certification. Review [requirements](#requirements) -before purchasing or deploying. +**Explore the integration in an isolated test environment.** Use the sample to shape your +implementation and prepare for your organization's production-readiness review. Review +[requirements](#requirements) before deploying. ## Table of Contents @@ -18,7 +18,7 @@ before purchasing or deploying. - [Deploy and configure](#deploy-and-configure) - [Operate](#operate) - [Develop](#develop) - - [Optional designs](#optional-designs) + - [Advanced topics](#advanced-topics) - [Getting help](#getting-help) ## Features @@ -35,12 +35,14 @@ before purchasing or deploying. Before deploying: - Confirm [supported provider access and Microsoft's approved testing and activation procedure](docs/GETTING-STARTED.md#1-get-ready). - If either is unavailable, stop. -- Use a dedicated nonproduction tenant, subscription, application registration, and resource prefix. + These give your team the supported path for setup, validation, and activation. +- Create an isolated test environment: use a nonproduction tenant and subscription, register a + new application, and choose a unique resource prefix so this endpoint stays separate from + production and other deployments. - Check the required [administrator roles, Azure permissions, tools, regional availability, and quota](setup/docs/README.md#prerequisites-for-step-2). - Plan for Azure resource, telemetry, and provider charges. Use the [provider cost estimate](docs/COST-CALCULATOR.md) as planning input, not a billing report. - -Deployment does not activate policy. Keep Easy Auth enabled; never bypass caller validation to test. +- Keep Easy Auth enabled from deployment through live operation so every request is checked + against the approved Microsoft caller. ## Architecture @@ -61,8 +63,9 @@ Follow the **[five-step setup guide](docs/GETTING-STARTED.md)** in order: 4. [Connect and test](docs/GETTING-STARTED.md#4-connect-your-provider-and-test): configure provider authentication, then complete approved evaluation and live checks for each intended channel. 5. [Activate and hand over](docs/GETTING-STARTED.md#5-activate-and-hand-over): have the policy administrator activate only after validation; assign monitoring, renewal, and rollback owners. -**Deployment does not activate policy. Keep Easy Auth enabled**; never bypass it to test. -See the [sample's production limitations](docs/CONTRACT.md#production-limitations). +After validation, your policy administrator can activate the endpoint through the approved +procedure. Review the [production-readiness considerations](docs/CONTRACT.md#production-limitations) +as you plan the live service. ## Guidance @@ -90,13 +93,14 @@ See the [sample's production limitations](docs/CONTRACT.md#production-limitation - [Application log events and safe fields](docs/LOGGING.md) - [Provider-neutral BYOT integration](docs/BRING-YOUR-OWN-PROVIDER.md) -### Optional designs + +### Advanced topics - [Multi-region routing with Azure Front Door](docs/FRONTDOOR.md) - [Conservative primary-to-secondary provider fallback](docs/MULTI-PROVIDER-IMPLEMENTATION.md) -Start with the validated single-region endpoint, then implement either design through its -capacity, validation, operations, and rollback guidance. +Build on the validated single-region endpoint with regional routing or a carefully controlled +provider-fallback strategy. Each guide covers capacity, validation, operations, and rollback. ## Getting help diff --git a/docs/APPLICATION-INSIGHTS.md b/docs/APPLICATION-INSIGHTS.md index 31f9097..72f579e 100644 --- a/docs/APPLICATION-INSIGHTS.md +++ b/docs/APPLICATION-INSIGHTS.md @@ -87,7 +87,7 @@ different sampling behavior and dimensions; a chart and a raw row count need not **Azure resource metrics** come from the platform and are queried under the relevant resource in Azure Monitor Metrics. Function execution/instance/memory metrics depend on the hosting plan; FC1 and EP1 do not expose identical sets. Key Vault has its own request/latency signals, Storage -has transaction/availability/latency signals, and optional Front Door has edge/origin signals. +has transaction/availability/latency signals, and Front Door adds edge/origin signals. These are not automatically Application Insights dependencies or custom metrics. Resource logs are another independent source. Depending on the resource and diagnostic setting's @@ -124,8 +124,9 @@ Review the following in the portal without assuming that resource creation prove controlled live test. Evaluation skips the provider send; it cannot validate that dependency path. Background credential refresh may run independently during either test. -Do not weaken Easy Auth or send periodic OTPs just to populate telemetry. A generic unauthenticated -URL probe does not validate the endpoint's authenticated, encrypted EPP contract. +Keep Easy Auth and the approved delivery flow intact while validating telemetry. Use an authorized +evaluation request for the authenticated, encrypted EPP path rather than generating periodic OTPs +or relying on a generic unauthenticated URL probe. ### Identity and ingestion @@ -177,7 +178,7 @@ Use the [cross-workspace queries and missing-origin check](MONITORING.md#3-multi with explicit sources, authorized access to each, and per-origin grouping. Check idle/standby origins too: no traffic and broken collection can both look empty. -For [optional Front Door](FRONTDOOR.md), inspect +For the [Front Door multi-region design](FRONTDOOR.md), inspect [edge metrics and logs separately](MONITORING.md#8-optional-azure-front-door-layer). Edge/WAF rejections, origin-connectivity failures, and probes need not appear as Function requests. Origin health does not prove provider authentication or delivery. diff --git a/docs/BRING-YOUR-OWN-PROVIDER.md b/docs/BRING-YOUR-OWN-PROVIDER.md index 7a56472..ed4f652 100644 --- a/docs/BRING-YOUR-OWN-PROVIDER.md +++ b/docs/BRING-YOUR-OWN-PROVIDER.md @@ -212,10 +212,11 @@ invalid or tampered payloads, evaluation without a send, and controlled live tes Check handset receipt or voice audio separately from acceptance. For local tests, use test keys and fake service responses on loopback only, without tunnels; Core Tools has no Easy Auth. -The sample has **no early acknowledgement/background delivery, durable queue, automatic send -retry, or deduplication**. Its outbound timeout is not a whole-request deadline. A timed-out send -may already have been accepted, so retries or caller fallback can cause duplicates. +The sample completes the request after provider acceptance. If your live design adds early +acknowledgement, background delivery, a durable queue, retries, or deduplication, define their +timing and ownership explicitly. The outbound timeout covers the provider call rather than the +whole request; investigate a timed-out send before retrying because it may already be accepted. Keep phone numbers, messages, passcodes, nonces, tokens, keys, JWE contents, and raw service responses out of logs. Disable body tracing in the application, platform, and proxies. -Review the remaining [production limitations](CONTRACT.md#production-limitations) before activation. +Review the [production-readiness considerations](CONTRACT.md#production-limitations) before activation. diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md index 0906848..01f137e 100644 --- a/docs/CONTRACT.md +++ b/docs/CONTRACT.md @@ -3,7 +3,8 @@ [Documentation](README.md) | [Implementation walkthrough](BRING-YOUR-OWN-PROVIDER.md) | [Settings and packages](../TECHNICAL.md) This page defines the shared contract for [JavaScript](../javascript/), [Python](../python/) and -[.NET](../dotnet/). See [production limitations](#production-limitations) before production use. +[.NET](../dotnet/). Use the [production-readiness considerations](#production-limitations) when +planning a live service. Go to [request/response](#1-http-api), [HTTP outcomes](#2-outcome--http-status-mapping), [provider contract](#3-provider-contract), [settings](#4-configuration-app-settings--env), @@ -517,28 +518,31 @@ wrong issuer or audience, unauthorized caller, HTTPS enforcement and SendOtp rou An authorized evaluation must succeed without provider I/O. These checks do not replace provider integration or handset-delivery checks. See [deployment verification](ONBOARDING.md#4-package-deploy-and-verify). -## Production limitations - -This is a sample, not production certification. Successful provider acceptance and nonce checks do -not prove handset delivery or support for every provider feature. - -- The Function imports one private PEM through a Key Vault secret reference and decrypts in-process; - vault-resident cryptographic operations and overlapping key rotation are not implemented. -- The sample waits for provider acceptance before replying. It has no durable queue, early - acknowledgement/background delivery handoff, or automatic send retries. Confirm the current - product-approved timing/activation requirements with Microsoft; deployment alone does not - establish that the sample meets them. -- The outbound timeout is not an end-to-end deadline. Cold starts, platform authentication and Key - Vault access can exceed the caller's budget; Python uses connect/read inactivity timeouts. -- Credential caches are process-local and proactive preparation is best-effort, not an Azure - traffic-readiness guarantee. Provider delivery still waits for acceptance; background refresh - is not background OTP delivery, a queue, or protection against duplicate sends. -- Voice is provider-specific: Soprano extracts the first six consecutive digits; Telesign paces - standalone six-digit numeric runs and repeats the full message. Test supported locales, - passcode format, and actual audio; these transforms do not guarantee spoken clarity. -- Full body-size/content-type and E.164 validation, subscription provisioning, certification, - least-cost routing and voice-callback workflows are outside this sample. Native fallback belongs - to the caller, not this Function. + +## Production-readiness considerations + +Use this checklist to plan the capabilities your production design may need beyond the sample. +Provider acceptance and nonce checks confirm important stages of the request, while handset +delivery and provider-specific features need their own validation. + +- **Encryption and rotation:** the Function imports one private PEM through a Key Vault secret + reference and decrypts in-process. A production design can add vault-resident cryptographic + operations and overlapping key rotation when required. +- **Delivery workflow:** the sample waits for provider acceptance before replying. Add a durable + queue, early acknowledgement/background handoff, or automated retry strategy only when the + approved product design defines their timing and duplicate-send behavior. +- **End-to-end timing:** size the caller budget for cold starts, platform authentication, Key Vault + access, and the provider request. The outbound timeout covers the provider call rather than the + full invocation; Python uses connect/read inactivity timeouts. +- **Credential readiness:** caches are process-local and proactive preparation is best-effort. + Provider delivery still waits for acceptance; background credential refresh is separate from + OTP delivery, queuing, and duplicate-send protection. +- **Voice experience:** Soprano extracts the first six consecutive digits; Telesign paces + standalone six-digit numeric runs and repeats the full message. Validate supported locales, + passcode formats, and actual audio for the intended experience. +- **Additional production capabilities:** add the body-size/content-type and E.164 validation, + subscription provisioning, certification, least-cost routing, or voice-callback workflows your + design requires. Native caller fallback remains with the caller rather than this Function. See [setup compatibility](ONBOARDING.md#setup-script-compatibility) for credential provisioning, endpoint format and unsupported setup options. diff --git a/docs/FRONTDOOR.md b/docs/FRONTDOOR.md index 8a76f5e..33c2aa4 100644 --- a/docs/FRONTDOOR.md +++ b/docs/FRONTDOOR.md @@ -1,4 +1,6 @@ -# Optional multi-region onboarding with Azure Front Door +# Multi-region onboarding with Azure Front Door + + [Documentation](README.md) | [Single-region setup](GETTING-STARTED.md) | [Routing troubleshooting](../setup/docs/Troubleshooting.md#optional-front-door-deployments) @@ -86,7 +88,8 @@ duplication does not add provider failover or remove those dependencies. ## 1. Plan the regional deployment -Start in a dedicated nonproduction tenant and subscription. Review the +Build the regional design in an isolated nonproduction tenant and subscription, separate from +live traffic. Review the [setup prerequisites](../setup/docs/README.md#prerequisites-for-step-2) and [regional quota guidance](../setup/docs/Troubleshooting.md#deployment-fails-with-subscriptionisoverquotaforsku). @@ -103,10 +106,10 @@ Start in a dedicated nonproduction tenant and subscription. Review the caller application ID, certificate/public-key identity, selected provider route, and regional owners. - Plan manual rollout, validation, certificate renewal, monitoring, and rollback before activation. -**Do not simply rerun `Setup-Epp.ps1` in another region against the same application.** It is a -single-region setup flow and does not coordinate Front Door, shared key material, regional -registrations, or failover. Independent certificate issuance or a setup rerun can change app -configuration or disable incoming access to an existing endpoint. +Use the regional procedure in this guide rather than rerunning `Setup-Epp.ps1` against the same +application. The setup script creates one region; the steps below coordinate Front Door, shared +key material, regional registrations, and failover without replacing an existing endpoint's +application configuration or certificate unexpectedly. ### Capacity gate: each region must support the combined peak load @@ -315,10 +318,10 @@ but is not a substitute for the separate capacity and dependency tests. ## 6. Regional failover fire-drill -This runbook is for an approved, dedicated nonproduction deployment. It exercises health-based -rerouting; it is not permission to disrupt production or a claim of a full Azure regional-outage -test. Define numeric pass/abort thresholds and durations before starting, rather than adopting the -observed failover timings below as an SLA. +Run this drill in an approved nonproduction environment built for failover testing. It exercises +health-based rerouting without affecting production and covers the documented scenario rather than +every possible regional outage. Define numeric pass/abort thresholds and durations before starting +instead of treating the observed timings below as an SLA. 1. **Approve the boundary.** Name the drill, change, telemetry, and restoration owners plus a backup. Record the exact test resources, window, isolation deadline, and restoration steps. diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index f42f991..0417e95 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -14,14 +14,16 @@ Portal screenshots hide identifying details; labels and layout may differ. ## 1. Get ready -Start in a **dedicated nonproduction tenant and subscription**. +Create an **isolated test environment** in a nonproduction tenant and subscription. Use a new +application registration and a unique resource prefix so the endpoint is easy to identify and +remains separate from production and other deployments. Before purchasing or deploying, confirm access to a supported provider's EPP integration through [Microsoft Security Store](https://securitystore.microsoft.com/private-solutions). Arrange Microsoft's approved testing and activation procedure with your tenant administrator. -Buying an offer does not enable the tenant feature or deploy this sample. If access or the -approved procedure is unavailable, **stop here** and follow the -[access and support guidance](ONBOARDING.md#before-purchasing-or-deploying). +Buying an offer is one part of onboarding; tenant access, the approved procedure, and deployment +are completed separately. Confirm those paths before continuing, or use the +[access and support guidance](ONBOARDING.md#before-purchasing-or-deploying) to get them in place. In Security Store, open **Private solutions** and select your approved subscription. A listing does not guarantee [guided provider support](ONBOARDING.md#provider-credential-names). @@ -48,8 +50,9 @@ Microsoft Entra ID allowed-tenants preview. Graph reuses the Azure CLI login; no separate sign-in is needed. Setup can install Bicep after confirmation, but not Azure CLI. Assign an **onboarding owner** to coordinate administrators, provider, and Microsoft support. -Review the [sample limitations](CONTRACT.md#production-limitations): no production certification, -automatic send retries, duplicate-send protection, or automatic certificate renewal. +Review the [production-readiness considerations](CONTRACT.md#production-limitations) and plan any +capabilities your live design requires, including certification, durable delivery workflows, +duplicate-send protection, and certificate-renewal automation. ## 2. Register your application @@ -79,7 +82,7 @@ configuration. See [application registration details](../setup/docs/README.md#st Also keep your Azure subscription ID handy. The [setup values worksheet](ONBOARDING.md#inputs-you-supply-to-setup) explains where to find -each value. Use a new dedicated application and resource prefix for another independent endpoint; +each value. Use a new application and unique resource prefix for another independent endpoint; changing the provider or region on a rerun does not create a separate deployment. ## 3. Run guided setup @@ -101,7 +104,8 @@ Review the downloaded script, then run it: On a shared or multi-account workstation, use `.\Setup-Epp.ps1 -ForceAuthentication` to request an explicit Azure CLI device-code sign-in. Microsoft Graph reuses that CLI session. -Select the customer tenant/subscription, dedicated application **client ID**, and Azure region. +Select the customer tenant/subscription, the application **client ID** created for this endpoint, +and the Azure region. Refresh or sign in if the expected account is missing. Then choose: - **Provider and route:** enter or confirm the provider by name. **Tenant scope** means Global/EU @@ -181,18 +185,18 @@ Ask your onboarding owner to arrange the The [illustrated monitoring steps](MONITORING.md#find-the-linked-resources-in-the-portal) show where to open Application Insights and its Logs workspace. -This repository does not supply a self-service token tool for the authorized Microsoft caller. -An ordinary Azure CLI token is not a substitute. If the approved test procedure is unavailable -or any check fails, **do not activate the endpoint**. Do not blindly retry a timed-out live send: -the provider may already have accepted it. +Use Microsoft's approved caller and test procedure for these checks; an ordinary Azure CLI token +cannot represent that caller. Activate the endpoint after every required check passes. If a live +send times out, investigate its outcome before trying again because the provider may already have +accepted it. ## 5. Activate and hand over Have an **Authentication Policy Administrator** follow the [supported activation procedure](../setup/docs/README.md#step-3---manually-validate-and-activate-policy). -They must save the existing policy, apply the approved endpoint URL and application client ID, -preserve other settings, and read back and validate the change. **Setup does not activate policy.** -The EPP policy fields require Microsoft's assisted procedure; do not guess a Graph update. +They save the existing policy, apply the approved endpoint URL and application client ID, +preserve other settings, and read back and validate the change. Guided setup prepares the endpoint; +the policy administrator completes activation through Microsoft's assisted procedure. Repeat for each authentication method you intend to enable. SMS and voice can share the same Function endpoint URL and application client ID; no additional setup run is needed for the second channel. @@ -212,4 +216,4 @@ Deleting Azure resources does not undo an authentication policy change. ## Find more detail Use the [task-based documentation index](README.md) for operations, developer references, -and optional designs. For a failed step, go directly to [troubleshooting](../setup/docs/Troubleshooting.md). +and advanced topics. For a failed step, go directly to [troubleshooting](../setup/docs/Troubleshooting.md). diff --git a/docs/MONITORING.md b/docs/MONITORING.md index b53a543..8c7f5d3 100644 --- a/docs/MONITORING.md +++ b/docs/MONITORING.md @@ -39,7 +39,7 @@ Keep a private inventory with one row per expected regional endpoint: | Observed `AppRoleName` and `AppRequests.Name` | Filter this Function and its `SendOtp` request operation without mixing other apps or readiness probes. | | Runtime, hosting plan, deployment version, and owner | Explain schema differences, cold starts, releases, and escalation. | | Expected traffic or authorized synthetic-test cadence | Decide whether missing data is actionable, including standby origins. | -| Optional Front Door profile, endpoint, origin group, and origin | Associate routing and health with the regional Function. | +| Front Door profile, endpoint, origin group, and origin | Associate regional routing and health with the Function. | Do not derive the expected inventory solely from recent telemetry: an origin that never reports would be absent from that list. @@ -529,7 +529,7 @@ Keep identifiers, exports, and screenshots private. See [Workbook authoring][wor ## 7. Availability, authenticated Functions, and FC1 -**Never exempt `SendOtp` from Easy Auth or make it publicly anonymous for probes.** +Keep `SendOtp` protected by Easy Auth for every probe and test. An unauthenticated 401/403 demonstrates rejection, not a successful authorized evaluation or provider call. Standard availability tests do not perform the complete EPP token acquisition, encrypted-request creation, and nonce-validation @@ -545,8 +545,8 @@ For the actual authentication/decryption path, use the supported EPP test procedure and an authorized caller. An independently operated synthetic runner must be able to satisfy existing caller restrictions, acquire fresh credentials, construct a valid encrypted evaluation request, and verify the nonce without -recording it. If that caller capability is unavailable, record the monitoring -gap; do not broaden Easy Auth allowlists just to make testing convenient. +recording it. If the runner cannot meet those caller requirements, document the +monitoring gap and retain the approved Easy Auth allowlist. Record sanitized success/duration results through your approved monitoring pipeline and monitor the runner's schedule/failures separately. @@ -570,9 +570,10 @@ healthy but unused. Do not open origin ingress to bypass its access restrictions Review the current [availability test capabilities][availability] before choosing Standard tests or a custom authenticated runner. -## 8. Optional Azure Front Door layer + +## 8. Regional edge monitoring with Azure Front Door -Only add this layer if using [manual Front Door onboarding](FRONTDOOR.md). +Use this layer with the [Front Door multi-region design](FRONTDOOR.md). For Standard/Premium, inspect the profile's Metrics for **Origin Health Percentage**, **Origin Latency**, **Request Count**, and response/error signals supported by the selected metric namespace. Confirm units and available origin/origin-group diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index 738ab16..9c09535 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -12,7 +12,7 @@ Guided setup has already deployed Azure resources and code. Next, complete | Find or check setup values | [Inputs](#inputs-you-supply-to-setup), [setup-managed values](#values-setup-configures-for-you) | | Connect the provider | [Credential names and handoff](#complete-provider-authentication) | | Test and activate | [Acceptance checks](#validate-the-deployed-endpoint), [operations handover](#activate-operate-and-retire) | -| Build a custom package | [Optional local/manual path](#optional-local-development-and-manual-deployment) | +| Build a custom package | [Custom development and deployment](#custom-development-and-manual-deployment) | ## Before purchasing or deploying @@ -40,9 +40,9 @@ Assign an **onboarding owner** in your organization to coordinate tenant/policy, provider administrators with Microsoft support. Obtain approved test and policy-change procedures before deployment; the repository supplies no named onboarding contact. -Review [production limitations](CONTRACT.md#production-limitations): no durable queue, automatic -send retries, deduplication, or whole-invocation deadline; one manually renewed decryption key. -Acceptance is not delivery, timeout fallback can duplicate sends, and setup is not certification. +Use the [production-readiness considerations](CONTRACT.md#production-limitations) to plan the +capabilities your live service needs, such as durable delivery, retry and deduplication strategy, +an end-to-end deadline, certificate renewal, and delivery confirmation. ## Values and ownership @@ -282,7 +282,8 @@ certificate contacts, or provider delivery receipts. Complete those manual opera relying on alerts. Keep the saved summary, approved policy snapshots, validation evidence, package/source version, and renewal dates in restricted operational storage. -## Optional local development and manual deployment + +## Custom development and manual deployment **Skip after guided setup unless customizing code.** Follow the diff --git a/docs/README.md b/docs/README.md index 47d7e46..2941659 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,8 +3,8 @@ [Sample overview and architecture](../README.md) **New deployment? Start with [Get started](GETTING-STARTED.md).** Before spending or deploying, -confirm supported access and Microsoft's approved testing and activation process. Use a dedicated -nonproduction environment. You will activate policy separately after validation. +confirm supported access and Microsoft's approved testing and activation process. Build the first +endpoint in an isolated nonproduction environment, validate it, and then complete policy activation. ## Table of Contents @@ -12,7 +12,7 @@ nonproduction environment. You will activate policy separately after validation. - [Deploy and validate](#deploy-and-validate) - [Operate](#operate) - [Develop](#develop) -- [Optional designs](#optional-designs) +- [Advanced topics](#advanced-topics) - [Get help](#get-help) ## Understand the sample @@ -20,7 +20,7 @@ nonproduction environment. You will activate policy separately after validation. - **Architecture and implementation choices:** [Technical reference](../TECHNICAL.md) - **Requests, responses, settings, and required behavior:** [HTTP contract](CONTRACT.md) - **Runtime events and safe diagnostic fields:** [Application log reference](LOGGING.md) -- **Security and production boundaries:** [Production limitations](CONTRACT.md#production-limitations) +- **Security and production planning:** [Production-readiness considerations](CONTRACT.md#production-limitations) ## Deploy and validate @@ -47,12 +47,13 @@ nonproduction environment. You will activate policy separately after validation. - **Requests and responses:** [HTTP contract](CONTRACT.md) - **Runtime events:** [Application log schema](LOGGING.md) - **Provider integration:** [Provider-neutral BYOT interface](BRING-YOUR-OWN-PROVIDER.md) -- **Production gaps:** [Sample limitations](CONTRACT.md#production-limitations) +- **Production planning:** [Readiness considerations](CONTRACT.md#production-limitations) -## Optional designs + +## Advanced topics -Build these designs from a validated single-region endpoint and follow their dedicated -implementation, validation, operations, and rollback guidance. +Extend a validated single-region endpoint with the design that fits your resiliency and provider +strategy. Each guide includes implementation, validation, operations, and rollback guidance. - **Route across regions:** [Multi-region Front Door design](FRONTDOOR.md) - **Add provider fallback:** [Multi-provider implementation](MULTI-PROVIDER-IMPLEMENTATION.md) diff --git a/docs/SIGN-IN-MONITORING.md b/docs/SIGN-IN-MONITORING.md index 1396829..15e91e4 100644 --- a/docs/SIGN-IN-MONITORING.md +++ b/docs/SIGN-IN-MONITORING.md @@ -7,7 +7,7 @@ | Investigate one sign-in without creating resources | [Choose tenant/access](#1-choose-your-tenant-and-access), then [find the sign-in](#2-find-one-sign-in) | | Set up ongoing identity alerts | [Recommended monitors](#recommended-monitors), then [export](#3-export-logs-for-ongoing-monitoring) and follow Steps 4-6 | | Diagnose missing records or quiet alerts | [Check data](#4-check-your-data-and-choose-a-query), [monitor ingestion](#monitor-ingestion-separately) | -| Adapt a query or add advanced alerts | [Optional reference](#optional-reference) | +| Adapt a query or add advanced alerts | [Advanced monitoring reference](#advanced-monitoring-reference) | The six-step walkthrough builds one user sign-in alert. Treat it as a worked example: each additional monitor needs its own scope, baseline, and query. @@ -112,8 +112,8 @@ Choose each monitor's threshold from representative traffic, not a small export If the issue points to the endpoint or provider, follow the [Function monitoring guide](MONITORING.md#runtime-specific-log-discovery) separately. -**For a single investigation, stop here.** This portal-only path needs no workspace/export -or additional Azure Monitor collection costs. Continue for ongoing alerts or longer-term analysis. +For a single investigation, this portal view may be all you need. Continue when you want ongoing +alerts, longer-term analysis, or a shared monitoring view. ## 3. Export logs for ongoing monitoring @@ -241,7 +241,8 @@ IP-based detection for [later](#more-alert-types), once you understand this base Enable the ongoing alert only after these checks pass. Advanced options follow below. -## Optional reference + +## Advanced monitoring reference For query maintainers: [access/licensing](#tenant-setup-access-and-licensing), [external tenants](#prepare-the-external-tenant-access-path), [categories](#choose-additional-export-categories), diff --git a/dotnet/README.md b/dotnet/README.md index 62f9214..97f1381 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -78,7 +78,7 @@ privacy-safe logging, and response behavior. See [binding differences](../docs/C Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) for no-send validation: authentication and decryption still run, but request-path provider lookup, credentials, and HTTP do not. Live success waits for acceptance, **not handset receipt**; failures omit the nonce. -Never blindly retry a timeout. +Treat a timeout as uncertain; check the provider outcome before deciding whether to retry. For message/voice mapping, see the [Soprano delivery contract](../docs/CONTRACT.md#encrypteddeliverycontext-jwe) and [Telesign integration](../TECHNICAL.md#telesign-epp). @@ -126,4 +126,4 @@ method delegates to the base `SendJsonAsync` transport and maps the t `ProviderResult`; provider mapping code never handles `HttpResponseMessage` or raw JSON DOM types. Provider-specific response semantics stay in each provider; no shared response-mapping registry is used. Common async HTTP transport lives in `PhoneProviderBase`. See -[production limitations](../docs/CONTRACT.md#production-limitations) before production use. +[production-readiness considerations](../docs/CONTRACT.md#production-limitations) when planning a live service. diff --git a/javascript/README.md b/javascript/README.md index 8ce7b55..baff1fa 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -73,7 +73,7 @@ The handler never parses or echoes incoming `Authorization`. Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) for no-send validation: authentication and decryption still run, but request-path provider lookup, credentials, and HTTP do not. Live success waits for acceptance, **not handset receipt**; failures omit the nonce. -Never blindly retry a timeout. +Treat a timeout as uncertain; check the provider outcome before deciding whether to retry. For message/voice mapping and credential rules, see the [Soprano delivery contract](../docs/CONTRACT.md#encrypteddeliverycontext-jwe) and @@ -118,4 +118,4 @@ To add a provider, implement its fixed `name`, `authenticationMode`, `credential [providers/index.js](src/functions/providers/index.js). `interpretResponse` returns a `ProviderResult` containing the provider-owned outcome, endpoint HTTP status and one safe fixed failure classification. Raw API JSON stays inside the provider. See -[production limitations](../docs/CONTRACT.md#production-limitations) before production use. +[production-readiness considerations](../docs/CONTRACT.md#production-limitations) when planning a live service. diff --git a/python/README.md b/python/README.md index 44b5456..43a8a0e 100644 --- a/python/README.md +++ b/python/README.md @@ -72,7 +72,7 @@ The handler never parses or echoes incoming `Authorization`. Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) for no-send validation: authentication and decryption still run, but request-path provider lookup, credentials, and HTTP do not. Live success waits for acceptance, **not handset receipt**; failures omit the nonce. -Never blindly retry a timeout. +Treat a timeout as uncertain; check the provider outcome before deciding whether to retry. For message/voice mapping, see the [Soprano delivery contract](../docs/CONTRACT.md#encrypteddeliverycontext-jwe) and [Telesign integration](../TECHNICAL.md#telesign-epp). @@ -113,4 +113,4 @@ unset for local evaluation without background credential acquisition. Add a provider by subclassing `PhoneProviderBase`, declaring its credential specification, and implementing `build_request` and `map_response`. Return `ProviderResult` with the coarse endpoint outcome and a fixed safe failure classification. Raw provider JSON remains local to the provider. -See [production limitations](../docs/CONTRACT.md#production-limitations) before production use. +See [production-readiness considerations](../docs/CONTRACT.md#production-limitations) when planning a live service. diff --git a/setup/docs/README.md b/setup/docs/README.md index 90789fb..948021f 100644 --- a/setup/docs/README.md +++ b/setup/docs/README.md @@ -83,10 +83,11 @@ application. Setup does not automatically migrate keys or activate the replaceme ## Step 1 - manually create the application -Use a dedicated nonproduction tenant/subscription for the first deployment. +For the first deployment, choose a nonproduction tenant and subscription where testing is isolated +from production. 1. In the customer tenant's **Microsoft Entra admin center > App registrations**, register a - dedicated application with **Accounts in this organizational directory only** as its initial + new application for this endpoint with **Accounts in this organizational directory only** as its initial supported account type; setup changes it to organizational multi-tenant after approval. No redirect URI, client secret, API permission, app role, or enterprise-application configuration is required manually. diff --git a/setup/docs/Troubleshooting.md b/setup/docs/Troubleshooting.md index fb3d659..5f549e8 100644 --- a/setup/docs/Troubleshooting.md +++ b/setup/docs/Troubleshooting.md @@ -81,10 +81,11 @@ with the printed certificate ID and the same prefix; another **Replace** request Coordinate maintenance and evaluation for every endpoint sharing the app; cached/in-flight requests can still use a retired key. -## Optional Front Door deployments + +## Multi-region Front Door deployments -The single-region setup script does not provision Front Door or a multi-region readiness endpoint. -Use the [manual Front Door guide](../../docs/FRONTDOOR.md) for that option. +Build regional routing on top of a validated single-region endpoint by following the +[Front Door guide](../../docs/FRONTDOOR.md), including its readiness endpoint and origin checks. - A 401/403/404 probe is unhealthy. Check the readiness path, origin host header, and profile-pinned network restriction. Never exempt SendOtp or disable Easy Auth. From 3dcab419a38066e3593dedecc83494cff3f7fb3f Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 14:33:07 -0700 Subject: [PATCH 10/13] docs: simplify headings and labels Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 11 +++++------ TECHNICAL.md | 4 +++- docs/APPLICATION-INSIGHTS.md | 10 +++++++--- docs/BRING-YOUR-OWN-PROVIDER.md | 7 +++++-- docs/CONTRACT.md | 10 +++++++--- docs/FRONTDOOR.md | 17 ++++++++++------ docs/GETTING-STARTED.md | 4 +++- docs/MONITORING.md | 28 ++++++++++++++++++--------- docs/MULTI-PROVIDER-IMPLEMENTATION.md | 19 ++++++++++++------ docs/ONBOARDING.md | 9 ++++++--- docs/README.md | 4 ++-- docs/SIGN-IN-MONITORING.md | 18 +++++++++++------ dotnet/README.md | 4 +++- javascript/README.md | 4 +++- python/README.md | 4 +++- 15 files changed, 102 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index d298aae..9166d83 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ -# External Phone Provider: Azure Function Sample +# External Phone Provider Azure Function Sample Connect Microsoft Entra ID to your phone provider for SMS and voice one-time passwords. Deploy **one endpoint in one Azure region**, using JavaScript, C#, or Python. Guided setup handles the Azure resources and publishes your selected runtime package. -**Explore the integration in an isolated test environment.** Use the sample to shape your -implementation and prepare for your organization's production-readiness review. Review -[requirements](#requirements) before deploying. +**Use an isolated test environment.** The sample helps you evaluate the integration, plan your +implementation, and prepare for production. Review [requirements](#requirements) before deploying. ## Table of Contents @@ -99,8 +98,8 @@ as you plan the live service. - [Multi-region routing with Azure Front Door](docs/FRONTDOOR.md) - [Conservative primary-to-secondary provider fallback](docs/MULTI-PROVIDER-IMPLEMENTATION.md) -Build on the validated single-region endpoint with regional routing or a carefully controlled -provider-fallback strategy. Each guide covers capacity, validation, operations, and rollback. +Use these guides after validating the single-region endpoint. They cover regional routing, +provider fallback, capacity, validation, operations, and rollback. ## Getting help diff --git a/TECHNICAL.md b/TECHNICAL.md index 83e0cf1..176b72a 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -1,4 +1,6 @@ -# External Phone Provider: Technical Reference +# Technical reference + + [Documentation](docs/README.md) | [First deployment](docs/GETTING-STARTED.md) | [HTTP contract](docs/CONTRACT.md) diff --git a/docs/APPLICATION-INSIGHTS.md b/docs/APPLICATION-INSIGHTS.md index 72f579e..ef44ebc 100644 --- a/docs/APPLICATION-INSIGHTS.md +++ b/docs/APPLICATION-INSIGHTS.md @@ -1,4 +1,6 @@ -# Application Insights for the External Phone Provider +# Application Insights + + [Documentation](README.md) | [Monitoring walkthrough](MONITORING.md) | [Log schema](LOGGING.md) @@ -64,7 +66,8 @@ for your language: initialize exporter/instrumentations and credentials, then ve and duplicate suppression. Do not copy another runtime's host settings. For languages outside this sample, check Microsoft's corresponding support tab. -## Signals, tables, and metrics are different things + +## Telemetry signals and tables The following table maps **Application Insights Logs** names to **Log Analytics workspace** names. Table availability and contents depend on what was actually emitted and ingested. @@ -183,7 +186,8 @@ For the [Front Door multi-region design](FRONTDOOR.md), inspect Edge/WAF rejections, origin-connectivity failures, and probes need not appear as Function requests. Origin health does not prove provider authentication or delivery. -## Read-only queries to establish what is present + +## Telemetry coverage queries Run these examples from the **linked Log Analytics workspace's Logs** view. They use workspace table/column names (`TimeGenerated`, `AppRoleName`, `OperationId`, `Properties`), not the diff --git a/docs/BRING-YOUR-OWN-PROVIDER.md b/docs/BRING-YOUR-OWN-PROVIDER.md index ed4f652..7e1649b 100644 --- a/docs/BRING-YOUR-OWN-PROVIDER.md +++ b/docs/BRING-YOUR-OWN-PROVIDER.md @@ -1,4 +1,6 @@ -# BYOT integration: the Entra ID request and response +# Bring your own provider + + [Documentation](README.md) | [Exact HTTP contract](CONTRACT.md) | [Runtime guides](README.md#develop) @@ -178,7 +180,8 @@ or failure. Keep credential caching in the existing services: credential specification; C# calls `FetchCredentialsAsync`. Do not add a separate credential cache or send-retry loop. Keep service secrets in Key Vault, not in settings or source. -### Configure, test, and package + +### Implementation steps 1. Start from [local.settings.sample.json](local.settings.sample.json). Copy it to an untracked `local.settings.json` beside your runtime's `host.json`, keep all `Values` entries as strings, diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md index 01f137e..9b62e59 100644 --- a/docs/CONTRACT.md +++ b/docs/CONTRACT.md @@ -1,4 +1,6 @@ -# External Phone Provider Function: Language-Agnostic Contract +# HTTP contract + + [Documentation](README.md) | [Implementation walkthrough](BRING-YOUR-OWN-PROVIDER.md) | [Settings and packages](../TECHNICAL.md) @@ -48,7 +50,8 @@ Forwarded headers, including `x-ms-client-principal`, do not establish trust by replace the required Easy Auth gate. The handler does not use them to authenticate callers or forward the incoming `Authorization` header to the provider. Configure Easy Auth as described in section 5. -### EPP request body: `Envelope` (cleartext envelope) + +### EPP request body | Field | Required | Notes | |-------|----------|-------| @@ -519,7 +522,8 @@ An authorized evaluation must succeed without provider I/O. These checks do not integration or handset-delivery checks. See [deployment verification](ONBOARDING.md#4-package-deploy-and-verify). -## Production-readiness considerations + +## Production readiness Use this checklist to plan the capabilities your production design may need beyond the sample. Provider acceptance and nonce checks confirm important stages of the request, while handset diff --git a/docs/FRONTDOOR.md b/docs/FRONTDOOR.md index 33c2aa4..04cb55d 100644 --- a/docs/FRONTDOOR.md +++ b/docs/FRONTDOOR.md @@ -1,6 +1,7 @@ -# Multi-region onboarding with Azure Front Door +# Azure Front Door multi-region deployment + [Documentation](README.md) | [Single-region setup](GETTING-STARTED.md) | [Routing troubleshooting](../setup/docs/Troubleshooting.md#optional-front-door-deployments) @@ -9,7 +10,7 @@ Start with a validated [single-region deployment](GETTING-STARTED.md), then use plan and implement the regional origins, readiness route, Front Door configuration, validation, failover drills, and rollback. Treat transition errors as part of the acceptance criteria. -Follow [scope](#scope-and-observed-behavior), [architecture](#architecture-one-url-multiple-origins), +Follow [scope](#scope-and-observed-behavior), [architecture](#architecture), [plan/capacity](#1-plan-the-regional-deployment), [origins/keys](#2-prepare-equivalent-independently-provisioned-origins), [readiness](#3-provide-a-non-delivering-readiness-endpoint), [routing](#4-configure-front-door-manually), [validation](#5-validate-before-manually-activating-policy), and [fire-drill](#6-regional-failover-fire-drill). @@ -43,7 +44,8 @@ isolation, survivor capacity, real-caller checks, and regional-outage evidence. set RTO/RPO, tolerable failures, latency, and evidence requirements; historical results provide test context rather than those targets. -## Architecture: one URL, multiple origins + +## Architecture ![Multi-region External Phone Provider architecture with Azure Front Door](images/multi-region-architecture.png) @@ -111,7 +113,8 @@ application. The setup script creates one region; the steps below coordinate Fro key material, regional registrations, and failover without replacing an existing endpoint's application configuration or certificate unexpectedly. -### Capacity gate: each region must support the combined peak load + +### Regional capacity **EACH region must be provisioned and validated to serve the combined peak workload on its own.** For a balanced two-region active-active baseline, this means **at least twice its normal per-region @@ -150,7 +153,8 @@ and credential/key refresh so warm caches do not hide a dependency on the failed this separate from the evaluation-only fire-drill; do not silently enable live sends in that drill or mark the full capacity gate passed using evaluation-only throughput. -## 2. Prepare equivalent, independently provisioned origins + +## 2. Prepare regional origins Create each regional Function App and its dependencies through your approved Azure process. Keep public access disabled while setting up authentication, keys, identities, and packages. @@ -251,7 +255,8 @@ Use the same request body and caller authorization end to end. Do not add header redirect, cache, or retry rules without separately validating their effect on authentication and OTP delivery. A cached response cannot confirm a new delivery request. Front Door does not cache POST. -### Restrict each origin to this Front Door profile + +### Restrict origin access Before allowing Front Door to reach the Functions, configure their access restrictions: diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index 0417e95..8321d9e 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -1,4 +1,6 @@ -# Set up an External Phone Provider endpoint +# Deployment guide + + [Documentation](README.md) | [Setup reference](../setup/docs/README.md) | [Troubleshooting](../setup/docs/Troubleshooting.md) diff --git a/docs/MONITORING.md b/docs/MONITORING.md index 8c7f5d3..298a240 100644 --- a/docs/MONITORING.md +++ b/docs/MONITORING.md @@ -1,4 +1,6 @@ -# Monitoring setup and sample queries +# Monitoring + + [Documentation](README.md) | [Collection diagnostics](APPLICATION-INSIGHTS.md) | [Log schema](LOGGING.md) | [Endpoint troubleshooting](../setup/docs/Troubleshooting.md#after-deployment-triage) @@ -28,7 +30,8 @@ These examples do not create monitors automatically. Review permissions, privacy before applying them. Thresholds and intervals are **starting points, not SLAs**; tune them to your deployment. -## 1. Record the deployment and confirm telemetry + +## 1. Record the deployment Keep a private inventory with one row per expected regional endpoint: @@ -49,7 +52,8 @@ Workbooks, or alerts. [Verify the deployed collection configuration](APPLICATION resource creation alone does not prove ingestion, and other deployment methods can differ. The template requests 30-day retention; check effective workspace/table retention and policy before changes. -### Find the linked resources in the portal + +### Portal resources 1. Open the Function App named in your deployment summary. 2. Select **Monitoring > Application Insights**. Follow the linked resource name; do not @@ -92,7 +96,8 @@ AppRequests Grant only required read access on **each workspace**. Workbook sharing does not grant data access; alert evaluation needs its own identity's access, not the author's. -### Run a first query and read the results + +### First query 1. In your workspace's **Logs**, close any welcome or Queries hub dialog. If the portal opens in **Agent** mode, switch it off for this walkthrough, then choose **KQL mode**. @@ -268,7 +273,8 @@ filtered, and an empty JSON result can mean a parser/pipeline mismatch. Check [runtime collection defaults](APPLICATION-INSIGHTS.md#what-the-checked-in-sample-configures) and [sampling limits](APPLICATION-INSIGHTS.md#sampling-privacy-and-collection-limits). -### Separate evaluated, accepted, and failed completions + +### Evaluation and delivery outcomes ```kusto AppTraces @@ -440,7 +446,8 @@ throttling, storage availability/latency, and credential/certificate expiry thro their respective resource monitoring. A successful Function response alone does not validate those dependencies' future readiness. -### Certificate, credential, and cost ownership + +### Operational ownership Keep `certificateExpiresUtc`, vault certificate/secret versions, and Entra encryption credential expiry in a restricted inventory with a renewal owner and backup. Follow the @@ -527,7 +534,8 @@ replacing hardcoded windows. Scheduled alerts cannot depend on Workbook-only par Save to the approved resource group and verify both Workbook and data access for readers. Keep identifiers, exports, and screenshots private. See [Workbook authoring][workbooks]. -## 7. Availability, authenticated Functions, and FC1 + +## 7. Availability monitoring Keep `SendOtp` protected by Easy Auth for every probe and test. An unauthenticated 401/403 demonstrates rejection, not a successful authorized @@ -571,7 +579,8 @@ Review the current [availability test capabilities][availability] before choosin Standard tests or a custom authenticated runner. -## 8. Regional edge monitoring with Azure Front Door + +## 8. Front Door monitoring Use this layer with the [Front Door multi-region design](FRONTDOOR.md). For Standard/Premium, inspect the profile's Metrics for **Origin Health Percentage**, @@ -599,7 +608,8 @@ as edge observations. Front Door probes do not perform an authenticated encrypte `SendOtp` evaluation or prove provider delivery. A probe path must follow the separate readiness design described above. See [Front Door monitoring][front-door]. -## 9. Sampling, ingestion gaps, privacy, and validation + +## 9. Sampling and data quality If data stops arriving, check the time range and workspace/component/role filters, actual traffic, deployment changes, log levels, host/worker exporter configuration, diff --git a/docs/MULTI-PROVIDER-IMPLEMENTATION.md b/docs/MULTI-PROVIDER-IMPLEMENTATION.md index 49d38ec..893bfb9 100644 --- a/docs/MULTI-PROVIDER-IMPLEMENTATION.md +++ b/docs/MULTI-PROVIDER-IMPLEMENTATION.md @@ -1,4 +1,6 @@ -# Add conservative primary-to-secondary provider fallback +# Provider fallback + + [Documentation](README.md) | [Normal deployment](GETTING-STARTED.md) | [Provider interface](BRING-YOUR-OWN-PROVIDER.md) @@ -18,7 +20,8 @@ Follow [policy](#1-set-a-fixed-provider-order-and-a-deny-by-default-policy), [deadline](#6-enforce-one-aggregate-deadline), and [validation](#7-validate-offline-then-deploy-only-after-separate-approval). [.NET/Python pointers](#net-and-python-integration-pointers) are not implemented fallback. -## 1. Set a fixed provider order and a deny-by-default policy + +## 1. Provider order Keep the existing HTTP registration in [SendOtp.js](../javascript/src/functions/SendOtp.js). Define the provider order in server-owned startup configuration: @@ -111,7 +114,8 @@ the system-assigned identity, grant that identity instead. Client-ID settings do identities or grant access. Complete provider account, recipient, sender and consent prerequisites; see [provider onboarding](ONBOARDING.md#complete-provider-authentication). -## 3. Give each context its own credential service and lifecycle + +## 3. Credential isolation Open [credentials.js](../javascript/src/functions/credentials.js). When caching is enabled, its singleton retains the first selected configuration; passing another account does not switch that cache. @@ -164,7 +168,8 @@ An evaluation request must not start credential acquisition; already-running refresh is independent. Rebuild contexts on controlled restart when configuration changes; never repurpose a live service for another account. -## 4. Add durable operation state before enabling fallback + +## 4. Operation state The repository has **no durable attempt store or idempotency contract**. Implement these before adding a second possible submission. Agree on a stable, authorized logical-operation key with @@ -199,7 +204,8 @@ external provider send: it is **not provider-side idempotency or true exactly-on Entra ID retries or native fallback outside this endpoint can still produce duplicates; confirm that behavior with the owner rather than promising the local guard prevents it. -## 5. Integrate one guarded fallback into the handler + +## 5. Fallback orchestration In [SendOtp.js](../javascript/src/functions/SendOtp.js), preserve authentication at the platform boundary, [envelope validation](../javascript/src/functions/entraPayload.js), @@ -283,7 +289,8 @@ deadline or cancellation changes after dispatch, record uncertainty and never fa Reserve enough time for the whole secondary attempt and finalization, rechecking before intent; insufficient budget means no secondary even when nonacceptance is otherwise eligible. -## 7. Validate offline, then deploy only after separate approval + +## 7. Validation and deployment Before editing, run the existing JavaScript tests from the repository root. Restore missing dependencies from the existing lockfile only when needed: diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index 9c09535..8e5c066 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -163,7 +163,8 @@ If the vault denies access, ask the approved credential administrator for help; vault protection or copy credentials into Function settings. See the permission and runtime checks in the provider-specific steps below. -### Telesign: enter and verify the two vault secrets + +### Telesign credentials 1. Confirm account access to each intended channel's EPP URL. Use the [vault steps above](#where-to-enter-api-key-credentials-in-key-vault) to store the raw API key as @@ -185,7 +186,8 @@ screenshots, or chat. During rotation, coordinate the matching pair and provider two secret updates are not atomic. Keep old credentials usable during the approved transition where the provider supports it, and verify live delivery before retiring them. -### Soprano: provider-administrator handoff + +### Soprano credentials Setup creates customer-side federation, **not** the commercial account or access in Soprano's tenant. Complete this handoff before treating OAuth as ready. @@ -259,7 +261,8 @@ runs and repeats the message. Verify the actual locale, spoken digits, and recip Do not blindly retry timeouts: provider acceptance can precede a lost response or caller fallback. Repeat deployed checks after package, authentication, credential, key, or routing changes. -## Activate, operate, and retire + +## Operations lifecycle Only after the acceptance checks pass, have the policy administrator follow [manual activation and rollback](../setup/docs/README.md#step-3---manually-validate-and-activate-policy). diff --git a/docs/README.md b/docs/README.md index 2941659..58ab60d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,8 +52,8 @@ endpoint in an isolated nonproduction environment, validate it, and then complet ## Advanced topics -Extend a validated single-region endpoint with the design that fits your resiliency and provider -strategy. Each guide includes implementation, validation, operations, and rollback guidance. +Use these guides after validating a single-region endpoint. Each one includes implementation, +validation, operations, and rollback guidance. - **Route across regions:** [Multi-region Front Door design](FRONTDOOR.md) - **Add provider fallback:** [Multi-provider implementation](MULTI-PROVIDER-IMPLEMENTATION.md) diff --git a/docs/SIGN-IN-MONITORING.md b/docs/SIGN-IN-MONITORING.md index 15e91e4..0e2a1ed 100644 --- a/docs/SIGN-IN-MONITORING.md +++ b/docs/SIGN-IN-MONITORING.md @@ -21,7 +21,8 @@ configure this export and its alerts after endpoint setup. Agree source tenant, exact identity/resource IDs, and notification owners, then implement in priority order. Do not scope by name fragments or every tenant application. -### Priority 1: Integration identities cannot sign in + +### Integration sign-in failures 1. **Watch:** application/service-principal and managed-identity authentication in **AADServicePrincipalSignInLogs** and **AADManagedIdentitySignInLogs**. Filter to the @@ -36,7 +37,8 @@ priority order. Do not scope by name fragments or every tenant application. there is not a sign-in event for every service request: only alert on absent successes after accounting for the expected schedule, caching, and [collection health](#monitor-ingestion-separately). -### Priority 2: Integration credentials, permissions, or trust change + +### Credential and permission changes 1. **Watch:** **AuditLogs** for the integration's application and service-principal objects. Include credential additions/removals, application identifier or trust-setting updates, @@ -52,7 +54,8 @@ priority order. Do not scope by name fragments or every tenant application. records before escalating an unexplained change to security. This is change monitoring, not a credential-expiry inventory or automatic revocation rule. -### Priority 3: Users repeatedly have trouble signing in + +### Repeated user sign-in failures 1. **Watch:** **SigninLogs** for the customer-facing app and source tenant. Review **AADNonInteractiveUserSignInLogs** separately for background token/session problems; @@ -220,7 +223,8 @@ IP-based detection for [later](#more-alert-types), once you understand this base Review **Automatically resolve alerts** and [identity options](#query-identity-and-rule-options) before **Review + create**. Keep the rule disabled until ready for the test below. -## 6. Test, then hand over + +## 6. Test and hand over 1. Save the action group, open it, and select **Test**. Choose the notification receivers and wait for the result; also check that the team receives the message. Microsoft's @@ -266,7 +270,8 @@ reports for delivery/billing. Evaluation and live requests can both return HTTP -### Tenant setup, access, and licensing + +### Tenant access and licensing 1. Microsoft Entra ID roles and Azure RBAC are separate. Use the roles in [portal access](#1-choose-your-tenant-and-access) and [export setup](#3-export-logs-for-ongoing-monitoring). @@ -515,7 +520,8 @@ reviewed query, baseline, and response plan. -### Build a Workbook when you need a shared view + +### Workbook 1. Review the [Microsoft monitoring repository](https://github.com/microsoft/external-id-azure-monitor) with the query maintainer. Its templates are deployable ARM resources, not text to paste into diff --git a/dotnet/README.md b/dotnet/README.md index 97f1381..35eaec7 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -1,4 +1,6 @@ -# External Phone Provider Function: C# (.NET isolated worker) +# C# implementation + + [Documentation](../docs/README.md) | [Settings](#environment-configuration) | [Request behavior](#request-behavior) | [Source](#source) diff --git a/javascript/README.md b/javascript/README.md index baff1fa..bdfab79 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -1,4 +1,6 @@ -# External Phone Provider Function: JavaScript +# JavaScript implementation + + [Documentation](../docs/README.md) | [Settings](#environment-configuration) | [Request behavior](#request-behavior) | [Source](#source-and-extension-points) diff --git a/python/README.md b/python/README.md index 43a8a0e..f1acfe2 100644 --- a/python/README.md +++ b/python/README.md @@ -1,4 +1,6 @@ -# External Phone Provider Function: Python (v2 model) +# Python implementation + + [Documentation](../docs/README.md) | [Settings](#environment-configuration) | [Request behavior](#request-behavior) | [Source](#source) From 856f01fcb4436ed57bc54d565a9609e748fcdd25 Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 14:43:06 -0700 Subject: [PATCH 11/13] docs: use neutral and direct language Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 6 +-- TECHNICAL.md | 8 ++-- docs/APPLICATION-INSIGHTS.md | 8 ++-- docs/BRING-YOUR-OWN-PROVIDER.md | 4 +- docs/CONTRACT.md | 6 +-- docs/COST-CALCULATOR.md | 7 ++-- docs/FRONTDOOR.md | 26 ++++++------ docs/GETTING-STARTED.md | 10 ++--- docs/LOGGING.md | 4 +- docs/MONITORING.md | 10 ++--- docs/MULTI-PROVIDER-IMPLEMENTATION.md | 2 +- docs/README.md | 2 +- docs/SIGN-IN-MONITORING.md | 60 +++++++++++++-------------- dotnet/README.md | 8 ++-- javascript/README.md | 8 ++-- python/README.md | 8 ++-- setup/docs/README.md | 8 ++-- setup/docs/Troubleshooting.md | 2 +- 18 files changed, 94 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 9166d83..74b2616 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ Connect Microsoft Entra ID to your phone provider for SMS and voice one-time pas Deploy **one endpoint in one Azure region**, using JavaScript, C#, or Python. Guided setup handles the Azure resources and publishes your selected runtime package. -**Use an isolated test environment.** The sample helps you evaluate the integration, plan your -implementation, and prepare for production. Review [requirements](#requirements) before deploying. +**Use an isolated test environment.** The sample supports integration evaluation, implementation +planning, and production preparation. Review [requirements](#requirements) before deployment. ## Table of Contents @@ -34,7 +34,7 @@ implementation, and prepare for production. Review [requirements](#requirements) Before deploying: - Confirm [supported provider access and Microsoft's approved testing and activation procedure](docs/GETTING-STARTED.md#1-get-ready). - These give your team the supported path for setup, validation, and activation. + These define the supported path for setup, validation, and activation. - Create an isolated test environment: use a nonproduction tenant and subscription, register a new application, and choose a unique resource prefix so this endpoint stays separate from production and other deployments. diff --git a/TECHNICAL.md b/TECHNICAL.md index 176b72a..31feaf2 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -4,8 +4,8 @@ [Documentation](docs/README.md) | [First deployment](docs/GETTING-STARTED.md) | [HTTP contract](docs/CONTRACT.md) -Use this page when you need implementation details, app settings, or package guidance. -Go to [implementations](#implementations), [guided setup](#guided-epp-setup), +This reference covers implementation details, app settings, and packages. +Sections: [implementations](#implementations), [guided setup](#guided-epp-setup), [download](#download-a-function-zip), [build](#build-zips-locally), [settings](#configure-environment-variables), [provider details](#provider-specific-integration-details), or [security](#security). For regional continuity, see @@ -126,7 +126,7 @@ first-party tests are excluded. Generated ZIPs are ignored by Git. JavaScript installs production dependencies from the lockfile in a temporary folder; your working `node_modules` is not copied or modified. Dependency lifecycle scripts are disabled for this sample's -JavaScript dependencies. If you add native dependencies or packages requiring install scripts, +JavaScript dependencies. When adding native dependencies or packages that require install scripts, review packaging and build them for the target Azure OS. **.NET is a source ZIP, not compiled output.** It contains `dotnet.csproj`, `host.json`, `Program.cs`, @@ -152,7 +152,7 @@ The same `-OutputPath` option works for all three scripts. Configure the destina app settings, Key Vault access, and Easy Auth separately before deployment. See [deployment and validation](docs/ONBOARDING.md#4-package-deploy-and-verify). Packaging success does not verify cloud configuration or provider delivery. File selection is tailored to this sample; -extend it deliberately if you add runtime assets, and never put secrets in application source. +extend it deliberately when adding runtime assets, and never put secrets in application source. ## The design in one line diff --git a/docs/APPLICATION-INSIGHTS.md b/docs/APPLICATION-INSIGHTS.md index ef44ebc..87e08b8 100644 --- a/docs/APPLICATION-INSIGHTS.md +++ b/docs/APPLICATION-INSIGHTS.md @@ -4,8 +4,8 @@ [Documentation](README.md) | [Monitoring walkthrough](MONITORING.md) | [Log schema](LOGGING.md) -Looking for logs or alerts? Start with the [monitoring walkthrough](MONITORING.md). -If telemetry is missing or incomplete, start with +For logs and alerts, use the [monitoring walkthrough](MONITORING.md). +For missing or incomplete telemetry, use [single-region configuration](#single-region-configuration). This page covers JavaScript, .NET isolated, and Python. @@ -16,7 +16,7 @@ not prove delivery either. This page assumes the endpoint is already deployed. Use the [setup guide](GETTING-STARTED.md) first, then follow the [approved test handoff](ONBOARDING.md#validate-the-deployed-endpoint). -On this page: [collection pipelines](#how-telemetry-reaches-application-insights), +Sections: [collection pipelines](#how-telemetry-reaches-application-insights), [signals and tables](#signals-tables-and-metrics-are-different-things), [single-region checks](#single-region-configuration), [ingestion identity](#identity-and-ingestion), [multi-region layout](#multi-region-configuration), [coverage queries](#read-only-queries-to-establish-what-is-present), @@ -62,7 +62,7 @@ exporters can change formatting, casing, and which properties survive. To add direct worker instrumentation, follow the [Functions OpenTelemetry guidance](https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto) -for your language: initialize exporter/instrumentations and credentials, then verify correlation +for the selected language: initialize exporter/instrumentations and credentials, then verify correlation and duplicate suppression. Do not copy another runtime's host settings. For languages outside this sample, check Microsoft's corresponding support tab. diff --git a/docs/BRING-YOUR-OWN-PROVIDER.md b/docs/BRING-YOUR-OWN-PROVIDER.md index 7e1649b..c8c2940 100644 --- a/docs/BRING-YOUR-OWN-PROVIDER.md +++ b/docs/BRING-YOUR-OWN-PROVIDER.md @@ -7,7 +7,7 @@ **Microsoft Entra ID** sends your **External Phone Provider (EPP)** endpoint an encrypted phone number and one-time passcode message. Your endpoint verifies the caller, decrypts the details, and either evaluates the request without sending or uses your phone service. -It then responds to Entra ID. **Your team owns the connection to that service.** +It then responds to Entra ID. **The customer owns the connection to that service.** Before deploying, ask your tenant administrator to confirm [eligibility, testing, and activation with Microsoft](ONBOARDING.md#before-purchasing-or-deploying). @@ -96,7 +96,7 @@ the success response below **without sending anything**. This request path skips selection, credential lookup, and outbound HTTP; no service configuration is needed. Configured workers may independently prepare or refresh credentials, but that does not send a message. -**Live (`mode: 1` or `"live"`):** your team implements and maintains the service integration. +**Live (`mode: 1` or `"live"`):** the customer implements and maintains the service integration. Map the requested SMS or voice delivery to your service, preserving the destination, message, and passcode. Handle voice rendering and locales as needed. Authenticate with your own service credentials; **never forward the incoming bearer token**. diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md index 9b62e59..001bae7 100644 --- a/docs/CONTRACT.md +++ b/docs/CONTRACT.md @@ -525,7 +525,7 @@ integration or handset-delivery checks. See [deployment verification](ONBOARDING ## Production readiness -Use this checklist to plan the capabilities your production design may need beyond the sample. +Use this checklist to plan capabilities required beyond the sample. Provider acceptance and nonce checks confirm important stages of the request, while handset delivery and provider-specific features need their own validation. @@ -545,8 +545,8 @@ delivery and provider-specific features need their own validation. standalone six-digit numeric runs and repeats the full message. Validate supported locales, passcode formats, and actual audio for the intended experience. - **Additional production capabilities:** add the body-size/content-type and E.164 validation, - subscription provisioning, certification, least-cost routing, or voice-callback workflows your - design requires. Native caller fallback remains with the caller rather than this Function. + subscription provisioning, certification, least-cost routing, or voice-callback workflows + required by the design. Native caller fallback remains with the caller rather than this Function. See [setup compatibility](ONBOARDING.md#setup-script-compatibility) for credential provisioning, endpoint format and unsupported setup options. diff --git a/docs/COST-CALCULATOR.md b/docs/COST-CALCULATOR.md index f2dbe54..439549c 100644 --- a/docs/COST-CALCULATOR.md +++ b/docs/COST-CALCULATOR.md @@ -48,7 +48,7 @@ approved folder. The following sample installs the required Microsoft Graph modules for the current user, connects to Microsoft Graph, identifies SMS and voice authentication steps, and matches each masked phone detail to a registered phone method. It caches phone-method lookups so -each user is queried only once. Replace `$start` and `$end` with the period you want to +each user is queried only once. Replace `$start` and `$end` with the required period to analyze. ```powershell @@ -216,9 +216,10 @@ Apply the provider's destination-specific rates to those estimated requests. Use rows when rates differ by destination, sender type, route, or message category. Include a contingency for growth, retries, fallback behavior, and seasonal peaks. -## Confirm the final estimate with your provider + +## Confirm provider rates -Work with your provider to validate: +Confirm the following with the provider: - How each international calling code maps to the provider's destination pricing. - Country and carrier coverage, sender registration, and route-specific rates. diff --git a/docs/FRONTDOOR.md b/docs/FRONTDOOR.md index 04cb55d..386e362 100644 --- a/docs/FRONTDOOR.md +++ b/docs/FRONTDOOR.md @@ -5,10 +5,10 @@ [Documentation](README.md) | [Single-region setup](GETTING-STARTED.md) | [Routing troubleshooting](../setup/docs/Troubleshooting.md#optional-front-door-deployments) -Use Azure Front Door when you want **one public EPP URL backed by Function Apps in multiple regions**. +Azure Front Door provides **one public EPP URL backed by Function Apps in multiple regions**. Start with a validated [single-region deployment](GETTING-STARTED.md), then use this guide to plan and implement the regional origins, readiness route, Front Door configuration, validation, -failover drills, and rollback. Treat transition errors as part of the acceptance criteria. +failover drills, and rollback. Transition errors are part of the acceptance criteria. Follow [scope](#scope-and-observed-behavior), [architecture](#architecture), [plan/capacity](#1-plan-the-regional-deployment), [origins/keys](#2-prepare-equivalent-independently-provisioned-origins), @@ -28,7 +28,7 @@ regional package and validate it before enabling probes. The released packages r single-region baseline; include runtime-specific validation for .NET or Python. The recorded trials cover encrypted evaluation, caller and origin restrictions, and controlled -Function stop/restart. Extend your acceptance plan to the features you will use: approved live +Function stop/restart. The acceptance plan must cover the required features: approved live SMS/voice checks, real Microsoft Entra caller behavior, regional dependency loss, custom domains, WAF or Private Link, and production-representative load. @@ -50,7 +50,7 @@ test context rather than those targets. ![Multi-region External Phone Provider architecture with Azure Front Door](images/multi-region-architecture.png) The diagram shows the design, not the exact test deployment. Its region names are examples. -Custom domains and Premium/WAF are optional and weren't part of the Standard-tier test. +Custom domains and Premium/WAF are optional and were not part of the Standard-tier test. Give each Function App, storage account, and Key Vault a globally unique name. Each region also has its own identity IDs and vault references. Keep the **provider and authentication configuration consistent and use the same RSA key**, rather than copying every setting literally. @@ -99,7 +99,7 @@ live traffic. Review the Security Store provider's requirements. A third region adds cost and capacity, but does not necessarily shorten failure detection. - Check availability of **every** required resource type, not just EP1 quota. Hosting availability - doesn't mean Application Insights or other dependencies are available there. + does not mean Application Insights or other dependencies are available there. - Budget for an EP1 plan and regional dependencies in each region, Front Door, and telemetry. Size and validate each region for the survivor workload described below, not just its normal share. - Choose one language and verify that each origin receives the same package. Do not mix @@ -193,14 +193,14 @@ certificate is separate from this JWE encryption certificate. - Give each Function identity scoped access to its regional vault. Set `EPP_DECRYPTION_KEY_PEM` to the regional certificate's **versioned PEM backing-secret reference**. - Verify that the public keys match and each region can decrypt a request. Matching vault names - or secret-version strings alone doesn't prove this. -- Restored certificates are independent copies. They don't stay in sync automatically. Coordinate renewal + or secret-version strings alone does not prove this. +- Restored certificates are independent copies. They do not stay in sync automatically. Coordinate renewal and Entra updates across every origin using the [certificate lifecycle guidance](../setup/docs/README.md#encryption-certificate-lifecycle). That section explains the single-key constraints; apply the multi-region updates through an approved manual procedure, not independent setup reruns. The sample has one active decryption key; it does not implement overlapping multi-key rotation. - Keep plaintext keys out of source, logs, tickets, and pipeline artifacts. Protect and remove - temporary encrypted backups under your approved handling policy. + temporary encrypted backups under the approved handling policy. ## 3. Provide a non-delivering readiness endpoint @@ -248,8 +248,8 @@ These are the **settings we tested**. Treat them as a starting point, not a perf | Caching | Disabled | Create the route under **Front Door manager**, associate it with the endpoint's domain, and select -the origin group. Inspect **Origin groups > your group > Origins** to see the regional backends. -You should see one public endpoint with several origins behind it. +the origin group. Inspect **Origin groups > selected group > Origins** to view the regional backends. +The profile should show one public endpoint with several origins. Use the same request body and caller authorization end to end. Do not add header-replacement, redirect, cache, or retry rules without separately validating their effect on authentication and OTP @@ -403,14 +403,14 @@ We saw no failed samples before the outages or after restarting the Functions. A failed request is not guaranteed to be replayed on another origin. The actual Entra caller's handling of these errors was not tested. -These trials didn't show a consistent benefit from 10-second probes or a third region. -More regions can add capacity and help tolerate additional failures, but they don't remove routing +These trials did not show a consistent benefit from 10-second probes or a third region. +More regions can add capacity and help tolerate additional failures, but they do not remove routing delays, shared provider dependencies, or application faults. Faster probes generate more traffic and can make brief problems trigger routing changes sooner. Decide how much disruption your application can accept, then repeat the tests from your callers' locations and at your expected load. Do not add blind retries to live sends: a timeout can occur after provider acceptance, and the sample -does not deduplicate deliveries. These results don't establish zero downtime, a production SLA, +does not deduplicate deliveries. These results do not establish zero downtime, a production SLA, or successful delivery to a phone. ## Operations and rollback diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index 8321d9e..1f0b02f 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -5,8 +5,8 @@ [Documentation](README.md) | [Setup reference](../setup/docs/README.md) | [Troubleshooting](../setup/docs/Troubleshooting.md) Deploy **one endpoint in one Azure region** for Microsoft Entra ID SMS and voice requests. -Guided setup downloads and publishes the selected package, so you can start without cloning or -building the repository. +Guided setup downloads and publishes the selected package without requiring a repository clone +or local build. SMS and voice can use different provider URLs on the same endpoint; validate each intended channel. Follow in order: [prepare](#1-get-ready), [register](#2-register-your-application), @@ -69,8 +69,8 @@ In the customer tenant's **Microsoft Entra admin center > App registrations > Ne ![App registration form with an example name, Single tenant only selected, and no redirect URI](images/onboarding/app-registration.png) -*Some portal versions label the account type **Single tenant only**. Choose your own directory. -The example above is an unsaved form, not an application you can reuse.* +*Some portal versions label the account type **Single tenant only**. Select the customer directory. +The example above is an unsaved form and cannot be reused.* Use these field labels to find the right IDs: @@ -133,7 +133,7 @@ Microsoft phone-provider service principal Microsoft Graph `Application.Read.All application-read permission. `No` or Enter cancels deployment; sign-in and prerequisite prompts are separate from this approval. -**Want to review without deploying?** Run interactively and choose **No** at that final prompt. +**Plan review without deployment:** run interactively and choose **No** at the final prompt. This still performs online preflight and downloads. There is no `-DryRun` or `-WhatIf` switch; see [plan review](../setup/docs/README.md#reviewing-the-plan-without-deploying) for details. diff --git a/docs/LOGGING.md b/docs/LOGGING.md index 72ab1bb..71eda28 100644 --- a/docs/LOGGING.md +++ b/docs/LOGGING.md @@ -2,8 +2,8 @@ [Documentation](README.md) | [Find deployed logs](MONITORING.md#runtime-specific-log-discovery) | [HTTP contract](CONTRACT.md) -Use this reference when implementing or interpreting endpoint events. For an incident, start with -the monitoring walkthrough, not a search for a universal JSON request summary. +This reference defines endpoint events and their interpretation. Incident investigation begins +with the monitoring walkthrough rather than a search for a universal JSON request summary. **Never log request bodies, decrypted fields, credentials, nonces, raw exceptions, or provider response bodies.** Keep SDK, platform, and proxy body tracing off. This page describes what the diff --git a/docs/MONITORING.md b/docs/MONITORING.md index 298a240..dee5465 100644 --- a/docs/MONITORING.md +++ b/docs/MONITORING.md @@ -4,15 +4,15 @@ [Documentation](README.md) | [Collection diagnostics](APPLICATION-INSIGHTS.md) | [Log schema](LOGGING.md) | [Endpoint troubleshooting](../setup/docs/Troubleshooting.md#after-deployment-triage) -Use this guide to find your endpoint's logs and set up alerts. Guided setup creates Application -Insights and a Log Analytics workspace; your operations team chooses, configures, and tests the +This guide covers endpoint logs and alert configuration. Guided setup creates Application +Insights and a Log Analytics workspace; the operations team chooses, configures, and tests notifications. For **tenant authentication and potential abuse signals**, use [Microsoft Entra ID sign-in monitoring](SIGN-IN-MONITORING.md). Endpoint setup does not configure that pipeline; sign-in failures do not by themselves identify provider failures or prove OTP delivery. -For your first single-region deployment: +For a first single-region deployment: 1. [Find the monitoring resources and confirm data is arriving](#1-record-the-deployment-and-confirm-telemetry) after an [authorized test](ONBOARDING.md#validate-the-deployed-endpoint). @@ -435,7 +435,7 @@ aggregation, window, evaluation frequency, dimensions, and action group. As a starting point, where the selected resource exposes the HTTP 5xx metric, alert on **Total > 5 over 5 minutes**, evaluated every minute. Use Application Insights request failure rules below when that platform signal is unavailable or -when you need the `SendOtp` operation filter. Add plan-appropriate execution, +when the `SendOtp` operation filter is required. Add plan-appropriate execution, memory, or instance signals only after establishing a baseline; there is no single CPU/instance threshold appropriate to both FC1 and EP1. @@ -479,7 +479,7 @@ condition and action group, and save only after reviewing estimated charges. Use an explicit query lookback consistent with the query's time filter. For the request summary examples, change the filter to `ago(5m)` in every source -and remove `bin(TimeGenerated, 5m)` from the grouping when you want **one summary +and remove `bin(TimeGenerated, 5m)` from the grouping for **one summary row per origin over the whole rolling window**. This avoids evaluating a partial calendar bucket as if it were a complete five-minute interval. For the single region query, remove the grouping entirely. Remove the final time sort in both diff --git a/docs/MULTI-PROVIDER-IMPLEMENTATION.md b/docs/MULTI-PROVIDER-IMPLEMENTATION.md index 893bfb9..8f2fcda 100644 --- a/docs/MULTI-PROVIDER-IMPLEMENTATION.md +++ b/docs/MULTI-PROVIDER-IMPLEMENTATION.md @@ -4,7 +4,7 @@ [Documentation](README.md) | [Normal deployment](GETTING-STARTED.md) | [Provider interface](BRING-YOUR-OWN-PROVIDER.md) -Use this guide when you need a customer-owned primary-to-secondary provider design. It adds code, +This guide describes a customer-owned primary-to-secondary provider design. It adds code, durable state, provider-specific approval, and dedicated testing around the existing `/api/SendOtp` URL. Keep the provider order on the server for one SMS channel rather than letting the caller select accounts or treating the order as a vendor ranking. diff --git a/docs/README.md b/docs/README.md index 58ab60d..ee533a8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,7 @@ [Sample overview and architecture](../README.md) -**New deployment? Start with [Get started](GETTING-STARTED.md).** Before spending or deploying, +**First deployment:** follow [Get started](GETTING-STARTED.md). Before spending or deploying, confirm supported access and Microsoft's approved testing and activation process. Build the first endpoint in an isolated nonproduction environment, validate it, and then complete policy activation. diff --git a/docs/SIGN-IN-MONITORING.md b/docs/SIGN-IN-MONITORING.md index 0e2a1ed..918929e 100644 --- a/docs/SIGN-IN-MONITORING.md +++ b/docs/SIGN-IN-MONITORING.md @@ -9,12 +9,12 @@ | Diagnose missing records or quiet alerts | [Check data](#4-check-your-data-and-choose-a-query), [monitor ingestion](#monitor-ingestion-separately) | | Adapt a query or add advanced alerts | [Advanced monitoring reference](#advanced-monitoring-reference) | -The six-step walkthrough builds one user sign-in alert. Treat it as a worked example: each -additional monitor needs its own scope, baseline, and query. +The six-step walkthrough is a worked example for one user sign-in alert. Each additional monitor +requires its own scope, baseline, and query. This guide covers Microsoft Entra authentication and configuration. Function execution and phone -delivery have their own monitoring paths. Keep identity details private; your administrators -configure this export and its alerts after endpoint setup. +delivery have separate monitoring paths. Identity details remain private; administrators configure +the export and alerts after endpoint setup. ## Recommended monitors @@ -90,7 +90,7 @@ Choose each monitor's threshold from representative traffic, not a small export ## 2. Find one sign-in 1. Open **Entra ID > Monitoring & health > Sign-in logs**. Choose a date range and filter - to the application you're investigating. Open a record you're authorized to inspect. + to the application under investigation. Open a record authorized for inspection. 2. In **Basic info**, check the **Application**, **Application ID**, **Resource**, outcome, and time. Keep the application's **client ID**, not its Object ID, for later query filters. This may not be the endpoint `applicationId` in your EPP deployment summary. @@ -115,8 +115,8 @@ Choose each monitor's threshold from representative traffic, not a small export If the issue points to the endpoint or provider, follow the [Function monitoring guide](MONITORING.md#runtime-specific-log-discovery) separately. -For a single investigation, this portal view may be all you need. Continue when you want ongoing -alerts, longer-term analysis, or a shared monitoring view. +A single investigation can end after the portal review. Ongoing alerts, longer-term analysis, +and shared monitoring require the remaining steps. ## 3. Export logs for ongoing monitoring @@ -181,12 +181,12 @@ alerts, longer-term analysis, or a shared monitoring view. for that window, not a whole week's total. There is no universal threshold; the source query is not a turnkey EPP or fraud detector. 5. Select **Save > Save as query** and choose the agreed query pack. Saving needs - **Log Analytics Contributor**; ask an authorized teammate if you only have read access. + **Log Analytics Contributor**; an authorized operator must make changes when access is read-only. See [saving queries](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/save-query). - Keep the filters, count units, baseline, and chosen threshold with your team's setup notes. + Keep the filters, count units, baseline, and chosen threshold with the monitoring setup notes. -You now have one scoped query to reuse for the first alert. Leave rate, volume, and -IP-based detection for [later](#more-alert-types), once you understand this baseline. +The scoped query can be reused for the first alert. Add rate, volume, and IP-based detection +only after establishing the [baseline](#more-alert-types). ## 5. Create your first alert @@ -202,13 +202,13 @@ IP-based detection for [later](#more-alert-types), once you understand this base table plan supports the query and rule. 3. For **Failed Signin reasons**, set **Measure** to **Calculation of a numeric column**, choose `Count`, and use **Total** across the result-code groups. - Leave **Split by dimensions** unset for this first combined alert. Don't count summary + Leave **Split by dimensions** unset for this first combined alert. Do not count summary rows as failures. Set **Static**, **Greater than**, and a **Threshold value** based on the normal counts for the same observation window in Step 4. 4. Set **Aggregation granularity (window size)** and **Frequency of evaluation** to match the team's observation window, ingestion delay, response needs, and budget. Review **Override query time range** with the query's time filter; alert scans are limited to - **48 hours**. Start with one evaluation above the threshold; if you need several, review the + **48 hours**. Start with one evaluation above the threshold; multiple evaluations require review of the [required query output and evaluation settings](#query-identity-and-rule-options) first. Review healthy and qualifying periods in Preview. Keep missing-data expectations separate from failure counts; [collection-health guidance](#monitor-ingestion-separately) explains why. @@ -219,7 +219,7 @@ IP-based detection for [later](#more-alert-types), once you understand this base 6. Choose the query **Identity**. For a **system-assigned managed identity**, clear **Enable upon creation**, create the rule disabled, and have your Azure administrator grant it reader/query access to the workspace. Wait for role propagation before enabling. - An interactive query working under your account doesn't prove the rule can read logs. + An interactive query working under the operator account does not prove the rule can read logs. Review **Automatically resolve alerts** and [identity options](#query-identity-and-rule-options) before **Review + create**. Keep the rule disabled until ready for the test below. @@ -230,12 +230,12 @@ IP-based detection for [later](#more-alert-types), once you understand this base and wait for the result; also check that the team receives the message. Microsoft's [testing instructions](https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/action-groups#test-an-action-group-in-the-azure-portal) cover failures and test permissions. This checks routing, not the sign-in query. -2. Test the query and a **temporary nonproduction rule** against existing records you're - allowed to inspect. Use a restricted test group and a temporary condition those records +2. Test the query and a **temporary nonproduction rule** against authorized existing records. + Use a restricted test group and a temporary condition those records satisfy. Check the query's window and portal override together. Verify the scheduled alert and its later resolution, then remove the temporary rule/settings. See the [full safe-test procedure](#validate-without-generating-provider-sends). -3. Don't generate live OTPs, repeated wrong codes, or production policy changes to populate +3. Do not generate live OTPs, repeated wrong codes, or production policy changes to populate a chart. If records are insufficient, use an approved **non-phone test sign-in** in a test tenant/app. An EPP evaluation does not guarantee a sign-in record or prove delivery. 4. Before enabling ongoing notifications, check that logs arrive, the rule identity has @@ -285,7 +285,7 @@ reports for delivery/billing. Evaluation and live requests can both return HTTP 3. The [external-tenant feature comparison](https://learn.microsoft.com/en-us/entra/external-id/customers/concept-supported-features-customers#general-feature-comparison) currently lists **ID Protection as unavailable in external tenants**. Full supported workforce [ID Protection capabilities](https://learn.microsoft.com/en-us/entra/id-protection/overview-identity-protection#license-requirements) - require P2 or an applicable entitlement. Don't buy workforce P2 expecting it to enable + require P2 or an applicable entitlement. Do not buy workforce P2 expecting it to enable external-tenant risk detections. ### Prepare the external-tenant access path @@ -302,7 +302,7 @@ reports for delivery/billing. Evaluation and live requests can both return HTTP name and region. In **Select user access**, choose the agreed users/groups and Azure roles. 3. Review the delegation, destination, region, permissions, and costs before **Create**. Wait for confirmation, select **Done**, then configure export as in Step 3. - Don't move the EPP subscription or alter authentication policy to imitate workforce setup. + Do not move the EPP subscription or alter authentication policy to imitate workforce setup. @@ -392,10 +392,10 @@ saving an alert query. Portal JSON downloads are not Log Analytics rows. previous authentication can satisfy a step; distinguish the step outcome from the whole sign-in. 2. Microsoft's [MFA Workbook template](https://github.com/microsoft/external-id-azure-monitor/blob/main/templates/mfaWorkbookDeployment.json) currently queries **AuditLogs**, not `SigninLogs` authentication-step arrays. It needs - corresponding audit export/activity. Don't substitute tables or read it as a full step sequence. + corresponding audit export/activity. Do not substitute tables or read it as a full step sequence. 3. A `SigninLogs` step report must handle `AuthenticationDetails` as the documented string containing parsed array data, with empty/invalid/missing data and nullable outcomes. - Array expansion counts **steps**, not sign-ins. Missing steps aren't success or failure. + Array expansion counts **steps**, not sign-ins. Missing steps are not success or failure. Keep `authenticationMethodDetail` and raw payloads out of shared views; they can contain phone identifiers and other personal data. @@ -405,17 +405,17 @@ saving an alert query. Portal JSON downloads are not Log Analytics rows. 1. In the [SigninLogs query collection](https://learn.microsoft.com/en-us/azure/azure-monitor/reference/queries/signinlogs), **Failed Signin reasons** groups by code/reason; **Failed login Count** counts failures by - resource; **Signin Locations** groups success/failure by location. Pick the measure you need. + resource; **Signin Locations** groups success/failure by location. Select the required measure. Some examples contain personal fields or limit MFA failures to one code; none is a complete endpoint/provider-failure detector. 2. Add your source tenant/app filters **before aggregation**. Use the [filtering tutorial](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-tutorial#apply-multiple-query-filters) or [Simple mode column filters](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-simple-mode#filter-by-column), - then switch to KQL to check them. Results-grid searches/header filters don't persist + then switch to KQL to check them. Results-grid searches/header filters do not persist as alert-query filters. Review numeric-looking code comparisons against the string schema. 3. Use the [time-range controls](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-tutorial#set-the-query-time-range) with any query time filter and verify the effective range. Preview **Show** limits and - browser-side grouping can hide data; don't mistake them for the full baseline. + browser-side grouping can hide data; do not treat them as the full baseline. 4. Save the reviewed query and share its location with a teammate who has log access. Check that they get the same filters and columns. Distinct-user, record-count, and step measures differ; note unknown-code handling and any exclusions alongside the query. @@ -444,7 +444,7 @@ saving an alert query. Portal JSON downloads are not Log Analytics rows. 5. In a new action group, complete **Basics**, **Notifications**, and any **Actions**, then **Review + create > Create**. Return to the rule and select the saved group. Check processing region, recipients, required verification, and **Common alert schema** - where applicable. Keep security results restricted; don't attach automatic policy changes + where applicable. Keep security results restricted; do not attach automatic policy changes to a heuristic. 6. **Automatically resolve alerts** makes the rule stateful; resolution timing depends on frequency. Review **Mute actions** with the receiving team. Edit an existing rule through @@ -482,13 +482,13 @@ reviewed query, baseline, and response plan. choosing a grouped rule. Account for unknown users, absent IPs, approximate distinct counts, distributed attempts, NAT, VPNs, mobile networks, and shared proxies. IP geolocation does not prove physical location. -3. Restrict personal-data results/notifications. Don't automatically block, disable an +3. Restrict personal-data results/notifications. Do not automatically block, disable an account/provider, or change policy solely on these heuristics. 4. In supported, licensed workforce tenants, follow [risk investigation instructions](https://learn.microsoft.com/en-us/entra/id-protection/howto-identity-protection-investigate-risk). Check populated `RiskLevelDuringSignIn`, `RiskLevelAggregated`, `RiskState`, and `RiskDetail`. - Detections can arrive asynchronously; empty, none, or hidden isn't fraud clearance. - A template named "risk detection" doesn't enable ID Protection in external tenants. + Detections can arrive asynchronously; empty, none, or hidden is not fraud clearance. + A template named "risk detection" does not enable ID Protection in external tenants. ### Monitor ingestion separately @@ -501,11 +501,11 @@ reviewed query, baseline, and response plan. arrival**; test each case. It is not a preconfigured no-data alert. An unresolved table or query error must remain visible, not become an empty-success fallback. 3. Create a separate rule with its own measure, threshold, lookback, frequency, recipient, - and maintainer. Match the query-time override to that calculation; don't blindly use + and maintainer. Match the query-time override to that calculation; do not blindly use the aggregation window. Check the query's frequency restrictions. 4. Inspect rule health/query errors, export settings, workspace access, transformations, ingestion limits, and service health. Fix evaluation failures separately from stale-data - incidents. Missing evidence doesn't mean less abuse. + incidents. Missing evidence does not mean less abuse. ### Validate without generating provider sends diff --git a/dotnet/README.md b/dotnet/README.md index 35eaec7..d974925 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -9,13 +9,13 @@ selected provider per deployment. Target: .NET 8 isolated worker, Azure Function ## Setup and deployment -**Deploying the sample?** Use the [five-step guide](../docs/GETTING-STARTED.md). +**Sample deployment:** use the [five-step guide](../docs/GETTING-STARTED.md). Guided setup builds with the .NET 8 SDK and publishes the Function. Next, complete [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). The [guided provider list](../docs/ONBOARDING.md#provider-credential-names) covers a subset of the adapters in this folder. -**Developing a custom package?** Follow these steps from `dotnet/`: +**Custom package development:** follow these steps from `dotnet/`: 1. Create the [local settings below](#environment-configuration). For live integration, choose an adapter's fixed `Name` from [Providers/](Src/Providers/), configure its endpoints/options, and @@ -87,9 +87,9 @@ and [Telesign integration](../TECHNICAL.md#telesign-epp). ## Source -Start with the provider-neutral +Use the provider-neutral [BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the Microsoft Entra ID payload, -caller authentication, and response rules. Your team owns the outbound service integration. +caller authentication, and response rules. The customer owns the outbound service integration. For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and [runtime-specific log discovery](../docs/MONITORING.md#runtime-specific-log-discovery). diff --git a/javascript/README.md b/javascript/README.md index bdfab79..f6ce7de 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -9,13 +9,13 @@ One configured provider owns its credentials, request mapping, and response inte ## Setup -**Deploying the sample?** Use the [five-step guide](../docs/GETTING-STARTED.md). +**Sample deployment:** use the [five-step guide](../docs/GETTING-STARTED.md). After guided setup, continue with [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). The [guided provider list](../docs/ONBOARDING.md#provider-credential-names) covers a subset of the adapters in this folder. -**Developing a custom package?** Follow these steps from `javascript/`: +**Custom package development:** follow these steps from `javascript/`: 1. Create the [local settings below](#environment-configuration). For live integration, choose a fixed provider ID from [providers/](src/functions/providers/), configure its endpoints/options, @@ -83,9 +83,9 @@ For message/voice mapping and credential rules, see the ## Source and extension points -Start with the provider-neutral +Use the provider-neutral [BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the Microsoft Entra ID payload, -caller authentication, and response rules. Your team owns the outbound service integration. +caller authentication, and response rules. The customer owns the outbound service integration. For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and the [JavaScript service-event queries](../docs/MONITORING.md#4-javascript-service-event-queries). diff --git a/python/README.md b/python/README.md index f1acfe2..4091dd5 100644 --- a/python/README.md +++ b/python/README.md @@ -9,13 +9,13 @@ selected provider per deployment. Target: Python 3.11, Azure Functions v4, Pytho ## Setup and deployment -**Deploying the sample?** Use the [five-step guide](../docs/GETTING-STARTED.md). +**Sample deployment:** use the [five-step guide](../docs/GETTING-STARTED.md). Guided setup builds remotely and publishes the Function without requiring a local Python install. Next, complete [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). The [guided provider list](../docs/ONBOARDING.md#provider-credential-names) covers a subset of the adapters in this folder. -**Developing a custom package?** Follow these steps from `python/`: +**Custom package development:** follow these steps from `python/`: 1. Create the [local settings below](#environment-configuration). For live integration, choose a fixed provider ID from [providers/](src/providers/), configure its endpoints/options, and @@ -81,9 +81,9 @@ and [Telesign integration](../TECHNICAL.md#telesign-epp). ## Source -Start with the provider-neutral +Use the provider-neutral [BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the Microsoft Entra ID payload, -caller authentication, and response rules. Your team owns the outbound service integration. +caller authentication, and response rules. The customer owns the outbound service integration. For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and [runtime-specific log discovery](../docs/MONITORING.md#runtime-specific-log-discovery). diff --git a/setup/docs/README.md b/setup/docs/README.md index 948021f..5382538 100644 --- a/setup/docs/README.md +++ b/setup/docs/README.md @@ -2,8 +2,8 @@ [Documentation](../../docs/README.md) | [First deployment](../../docs/GETTING-STARTED.md) | [Troubleshooting](Troubleshooting.md) -Use this page when you need details about setup choices, reruns, or maintenance. For a first -deployment, follow the five-step guide above; it handles the required source and Bicep files. +This reference covers setup choices, reruns, and maintenance. For a first deployment, follow the +five-step guide above; it handles the required source and Bicep files. | Task | Section | | --- | --- | @@ -18,8 +18,8 @@ deployment, follow the five-step guide above; it handles the required source and Confirm [provider access and the approved Microsoft procedure](../../docs/ONBOARDING.md#before-purchasing-or-deploying) before purchasing or deploying. A provider purchase alone does not enable the tenant feature or authorize its API. -Setup creates **one endpoint in one Azure region**. When you are ready to add regional continuity, -follow the [Front Door guide](../../docs/FRONTDOOR.md) to coordinate origins and encryption keys. +Setup creates **one endpoint in one Azure region**. Regional continuity uses the +[Front Door guide](../../docs/FRONTDOOR.md) to coordinate origins and encryption keys. ## Availability diff --git a/setup/docs/Troubleshooting.md b/setup/docs/Troubleshooting.md index 5f549e8..2de9fd7 100644 --- a/setup/docs/Troubleshooting.md +++ b/setup/docs/Troubleshooting.md @@ -2,7 +2,7 @@ [Documentation](../../docs/README.md) | [Setup reference](README.md) | [Resume onboarding](../../docs/GETTING-STARTED.md) -Start with the stage that failed. Keep existing resources in place, and never blindly retry a +Select the stage that failed. Keep existing resources in place, and never blindly retry a live send. Before rerunning setup, read [safe recovery](#recover-without-destructive-cleanup). After deployment, complete the provider and delivery checks separately. From 48dd4c7dad7bb96feff03a6a3a878f2b4ef4ca4c Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 14:55:25 -0700 Subject: [PATCH 12/13] docs: simplify and strengthen guidance Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 23 +++--- TECHNICAL.md | 40 +++++----- docs/APPLICATION-INSIGHTS.md | 12 +-- docs/BRING-YOUR-OWN-PROVIDER.md | 39 +++++----- docs/CONTRACT.md | 34 ++++----- docs/COST-CALCULATOR.md | 18 ++--- docs/FRONTDOOR.md | 102 ++++++++++++-------------- docs/GETTING-STARTED.md | 34 ++++----- docs/LOGGING.md | 13 ++-- docs/MONITORING.md | 22 +++--- docs/MULTI-PROVIDER-IMPLEMENTATION.md | 69 ++++++++--------- docs/ONBOARDING.md | 74 ++++++++++--------- docs/README.md | 6 +- docs/SIGN-IN-MONITORING.md | 8 +- dotnet/README.md | 25 ++++--- javascript/README.md | 28 +++---- python/README.md | 24 +++--- setup/docs/README.md | 47 ++++++------ setup/docs/Troubleshooting.md | 28 +++---- 19 files changed, 329 insertions(+), 317 deletions(-) diff --git a/README.md b/README.md index 74b2616..d3ff5f4 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # External Phone Provider Azure Function Sample -Connect Microsoft Entra ID to your phone provider for SMS and voice one-time passwords. -Deploy **one endpoint in one Azure region**, using JavaScript, C#, or Python. -Guided setup handles the Azure resources and publishes your selected runtime package. +Use this sample to connect Microsoft Entra ID to a phone provider for SMS and voice one-time +passwords. Guided setup deploys **one endpoint in one Azure region** and publishes the selected +JavaScript, C#, or Python package. -**Use an isolated test environment.** The sample supports integration evaluation, implementation -planning, and production preparation. Review [requirements](#requirements) before deployment. +**Start in a separate nonproduction environment.** This keeps testing away from live resources +while supporting evaluation, implementation planning, and production preparation. Review +[requirements](#requirements) before deployment. ## Table of Contents @@ -35,9 +36,8 @@ Before deploying: - Confirm [supported provider access and Microsoft's approved testing and activation procedure](docs/GETTING-STARTED.md#1-get-ready). These define the supported path for setup, validation, and activation. -- Create an isolated test environment: use a nonproduction tenant and subscription, register a - new application, and choose a unique resource prefix so this endpoint stays separate from - production and other deployments. +- Prepare a separate test environment with a nonproduction tenant and subscription, a new + application registration, and a unique resource prefix. - Check the required [administrator roles, Azure permissions, tools, regional availability, and quota](setup/docs/README.md#prerequisites-for-step-2). - Plan for Azure resource, telemetry, and provider charges. Use the [provider cost estimate](docs/COST-CALCULATOR.md) as planning input, not a billing report. - Keep Easy Auth enabled from deployment through live operation so every request is checked @@ -47,9 +47,10 @@ Before deploying: ![Microsoft Entra ID sends an encrypted request through Easy Auth to a Function, which uses Key Vault and calls the phone provider](docs/images/single-region-architecture.png) -*Easy Auth checks the Microsoft caller; the Function decrypts the request. -Live success means provider acceptance, not confirmed delivery; evaluation sends nothing. Region, credentials, caching, and -telemetry are illustrative: see [architecture details](TECHNICAL.md#single-region-architecture). +*Easy Auth checks the Microsoft caller, and the Function decrypts the request. +For live requests, success confirms provider acceptance; confirm delivery separately. Evaluation +sends nothing. Region, credentials, caching, and telemetry are illustrative: see +[architecture details](TECHNICAL.md#single-region-architecture). [Open the full-size diagram](docs/images/single-region-architecture.png).* ## Get started diff --git a/TECHNICAL.md b/TECHNICAL.md index 31feaf2..b7e74eb 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -33,14 +33,14 @@ neither of which is a successful evaluation. ## Resiliency and business continuity -Start with a validated single-region deployment. When regional continuity is required, the -[Azure Front Door design](docs/FRONTDOOR.md) describes one public URL across independently -provisioned regional origins while preserving the SendOtp contract. +Start with a validated single-region deployment. For regional continuity, the +[Azure Front Door design](docs/FRONTDOOR.md) uses one public URL across separate regional +origins while preserving the SendOtp contract. -The guide covers survivor capacity, shared encryption-key coordination, regional dependencies, -safe failover drills, and rollback. It also defines a dedicated non-delivering readiness endpoint -to implement and validate with each regional package. Build regional provisioning and readiness -into your approved BCDR process, then activate only after both directions pass the documented checks. +The guide covers capacity, shared encryption keys, regional dependencies, safe failover drills, +and rollback. It also defines a non-delivering readiness endpoint for each regional package. +Add regional deployment and readiness to the approved BCDR process, then activate after both +failover directions pass the documented checks. ## Implementations @@ -129,7 +129,7 @@ JavaScript installs production dependencies from the lockfile in a temporary fol JavaScript dependencies. When adding native dependencies or packages that require install scripts, review packaging and build them for the target Azure OS. -**.NET is a source ZIP, not compiled output.** It contains `dotnet.csproj`, `host.json`, `Program.cs`, +**The .NET download contains source code.** It includes `dotnet.csproj`, `host.json`, `Program.cs`, and the C# files under `Functions/` and `Src/`. Packaging does not run restore, build, or publish, and needs no .NET SDK. It excludes `bin/`, `obj/`, tests, local settings, and compiled dependencies. Customers extract it and run `dotnet publish dotnet.csproj --configuration Release --output ../publish` @@ -137,10 +137,10 @@ with the .NET 8 SDK, or use a deployment pipeline that builds the project. Deplo publish output with `host.json` at its root, not the source ZIP directly. CI tests this customer build from an extracted copy; that temporary publish output is not included in the download. -**Python is a source ZIP, not a ready-to-run package.** Deploy to a Linux Function App with remote -build enabled in the deployment tool for your hosting plan, so Azure installs `requirements.txt`. -Do not use this source ZIP directly with run-from-package or copy Windows-installed Python dependencies -to Azure. The script deliberately does not invoke pip or include a local virtual environment. +**The Python download contains source code.** Deploy it to a Linux Function App with remote build +enabled so Azure installs `requirements.txt`. Use the supported remote-build path instead of +run-from-package or Windows-installed dependencies. The packaging script does not run pip or +include a local virtual environment. Existing archives are never overwritten. For another build, specify a new path: @@ -279,9 +279,9 @@ For an SMS request, the adapter builds the following JSON from the decrypted del ``` `phoneNumber` must match `^\+[1-9][0-9]{1,14}$`; the leading `+` is preserved. SMS passes the complete -`message` unchanged as `message.text`, including whitespace. For Voice, each six-digit numeric run -that is not part of a longer number is rendered with comma-separated digits, and the complete paced -message is sent twice with one separating space. Telesign performs text-to-speech for Voice; no +`message` unchanged as `message.text`, including whitespace. For Voice, each standalone six-digit +number is rendered with comma-separated digits. The complete paced message is sent twice with one +separating space. Telesign performs text-to-speech for Voice; no separate speech object is needed. A nonblank string `locale` becomes `message.language`; otherwise language is omitted. The envelope channel selects the single `sms` or `voice` entry. `correlation_id` uses a nonempty string request @@ -295,11 +295,13 @@ omits that header on live sends and does not forward it from incoming requests. `mode: 2` evaluation path for Function tests without delivery: it skips provider HTTP and credential lookup entirely, rather than invoking Telesign shutter mode. -Responses normalize `reference_id` and `status.code`/`status.description` internally; provider +Responses normalize `reference_id` and `status.code`/`status.description` internally. Provider metadata is not exposed in the public nonce response. [Application logs](docs/LOGGING.md#safe-fields-and-identifiers) -include only the provider HTTP status, mapped status/outcome and the bounded raw provider reference -ID as `providerMessageId` for support lookup, never the raw response or description. Existing numeric success codes -are retained (SMS: 200, 203, 290-292; Voice: 100-103). EPP code `3001` ("Message in progress"), +include the provider HTTP status and mapped status/outcome. They also include a length-limited +provider reference ID as `providerMessageId` for support lookup. They exclude the raw response +and description. +Existing numeric success codes remain unchanged (SMS: 200, 203, 290-292; Voice: 100-103). +EPP code `3001` ("Message in progress"), observed for both channels, is also accepted on successful HTTP responses. This acknowledges provider acceptance, not handset receipt or completed audio playback. The sample's recognized codes are not a complete provider status-code catalog. Missing, malformed, or diff --git a/docs/APPLICATION-INSIGHTS.md b/docs/APPLICATION-INSIGHTS.md index 87e08b8..2804f9b 100644 --- a/docs/APPLICATION-INSIGHTS.md +++ b/docs/APPLICATION-INSIGHTS.md @@ -9,9 +9,9 @@ For missing or incomplete telemetry, use [single-region configuration](#single-region-configuration). This page covers JavaScript, .NET isolated, and Python. -Function telemetry shows execution, timing, and logs, **not recipient delivery**. -[Microsoft Entra ID sign-in monitoring](SIGN-IN-MONITORING.md) uses a separate tenant export pipeline and does -not prove delivery either. +Function telemetry shows execution, timing, and logs. Confirm recipient delivery through the +provider's supported delivery evidence. [Microsoft Entra ID sign-in monitoring](SIGN-IN-MONITORING.md) +uses a separate tenant export pipeline for identity activity. This page assumes the endpoint is already deployed. Use the [setup guide](GETTING-STARTED.md) first, then follow the [approved test handoff](ONBOARDING.md#validate-the-deployed-endpoint). @@ -62,9 +62,9 @@ exporters can change formatting, casing, and which properties survive. To add direct worker instrumentation, follow the [Functions OpenTelemetry guidance](https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto) -for the selected language: initialize exporter/instrumentations and credentials, then verify correlation -and duplicate suppression. Do not copy another runtime's host settings. For languages outside -this sample, check Microsoft's corresponding support tab. +for the selected language. Initialize the exporter, instrumentation, and credentials, then verify +correlation and duplicate suppression. Use the settings for that runtime. For languages outside +this sample, check the matching Microsoft support tab. ## Telemetry signals and tables diff --git a/docs/BRING-YOUR-OWN-PROVIDER.md b/docs/BRING-YOUR-OWN-PROVIDER.md index c8c2940..809c595 100644 --- a/docs/BRING-YOUR-OWN-PROVIDER.md +++ b/docs/BRING-YOUR-OWN-PROVIDER.md @@ -4,10 +4,10 @@ [Documentation](README.md) | [Exact HTTP contract](CONTRACT.md) | [Runtime guides](README.md#develop) -**Microsoft Entra ID** sends your **External Phone Provider (EPP)** endpoint -an encrypted phone number and one-time passcode message. Your endpoint verifies the caller, -decrypts the details, and either evaluates the request without sending or uses your phone service. -It then responds to Entra ID. **The customer owns the connection to that service.** +This guide shows how to connect a phone service to the **External Phone Provider (EPP)** +contract. **Microsoft Entra ID** sends the endpoint an encrypted phone number and one-time +passcode message. The endpoint verifies the caller, decrypts the details, and either evaluates +the request without sending or uses the connected phone service. Before deploying, ask your tenant administrator to confirm [eligibility, testing, and activation with Microsoft](ONBOARDING.md#before-purchasing-or-deploying). @@ -23,8 +23,8 @@ Then [register your integration](#where-to-add-your-integration) and [validate b Entra ID sends **`POST /api/SendOtp` over HTTPS**, with a signed bearer token in `Authorization`. In this sample, **Easy Auth** (Azure App Service Authentication) checks the token and authorizes -the caller before the Function runs. The Function's trigger is anonymous and has no backup -authentication check, so do not expose it with Easy Auth disabled or bypassed. +the caller before the Function runs. Keep Easy Auth enabled because the anonymous Function +trigger relies on this platform check. Require HTTPS and authentication, return `401` for unauthenticated requests, and restrict access to the trusted tenant issuer, endpoint-app audience, and authorized Microsoft caller application ID. @@ -84,7 +84,7 @@ The decrypted delivery context contains: | `riskContext` | No | Contextual JSON data; no shared schema validation, risk evaluation, or forwarding. | | `textToVoice` | No | Legacy speech data; no portable schema or shared forwarding. | -Do not assume an optional field is supported just because it appears in the request. +Treat optional fields as available only when the selected integration supports them. For parser and wrong-type handling, see [runtime binding differences](CONTRACT.md#runtime-binding-differences). @@ -101,14 +101,14 @@ Map the requested SMS or voice delivery to your service, preserving the destinat and passcode. Handle voice rendering and locales as needed. Authenticate with your own service credentials; **never forward the incoming bearer token**. -Wait for recognized acceptance before returning success. A queued or pending result counts -only if your integration recognizes it as acceptance. Unknown or incomplete responses must fail, -and a success-looking body on a failed HTTP response is not acceptance. Return the result through -the shared response behavior below, not as raw service JSON. +Return success after the integration recognizes provider acceptance. A queued or pending result +counts only when the integration defines it as accepted. Map unknown, incomplete, or failed HTTP +responses to an error, even when the response body looks successful. Use the shared response +behavior below instead of returning raw service JSON. -The sample uses one configured service per deployment. A custom integration does not add -guided setup support. Follow [Where to add your integration](#where-to-add-your-integration) -for the files to create, registration, settings, and packaging. +The sample uses one configured service per deployment. Follow +[Where to add your integration](#where-to-add-your-integration) for the source files, +registration, settings, packaging, and custom deployment steps. ## 4. Respond to Entra ID @@ -202,7 +202,7 @@ or send-retry loop. Keep service secrets in Key Vault, not in settings or source release. C# needs publish output; Python needs the documented Linux remote build. Follow [manual deployment](ONBOARDING.md#optional-local-development-and-manual-deployment) to deploy the custom package and apply cloud settings and vault permissions separately. - Retain the [Easy Auth trust checks](ONBOARDING.md#manual-deployment-trust-checklist), then + Keep the [Easy Auth trust checks](ONBOARDING.md#manual-deployment-trust-checklist), then complete the validation below. @@ -215,10 +215,11 @@ invalid or tampered payloads, evaluation without a send, and controlled live tes Check handset receipt or voice audio separately from acceptance. For local tests, use test keys and fake service responses on loopback only, without tunnels; Core Tools has no Easy Auth. -The sample completes the request after provider acceptance. If your live design adds early -acknowledgement, background delivery, a durable queue, retries, or deduplication, define their -timing and ownership explicitly. The outbound timeout covers the provider call rather than the -whole request; investigate a timed-out send before retrying because it may already be accepted. +The sample completes the request after provider acceptance. A live design can add early +acknowledgement, background delivery, a durable queue, retries, or duplicate-send protection. +Define the timing and owner for each feature. The outbound timeout covers the provider call, +not the whole request. Check a timed-out send before retrying because the provider may already +have accepted it. Keep phone numbers, messages, passcodes, nonces, tokens, keys, JWE contents, and raw service responses out of logs. Disable body tracing in the application, platform, and proxies. diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md index 001bae7..b210fce 100644 --- a/docs/CONTRACT.md +++ b/docs/CONTRACT.md @@ -46,9 +46,9 @@ the cleartext envelope carries routing/scheduling only. | `x-ms-correlation-id` | tracing only; fallback for envelope `correlationId`, not authentication | | `x-ms-client-request-id` | per-attempt tracing id (used as `messageId`), not authentication | -Forwarded headers, including `x-ms-client-principal`, do not establish trust by themselves and cannot -replace the required Easy Auth gate. The handler does not use them to authenticate callers or forward -the incoming `Authorization` header to the provider. Configure Easy Auth as described in section 5. +Easy Auth provides caller trust. Forwarded headers such as `x-ms-client-principal` provide context, +not authentication. The handler does not use them to authenticate callers or forward the incoming +`Authorization` header to the provider. Configure Easy Auth as described in section 5. ### EPP request body @@ -89,8 +89,8 @@ advisory key-ID check must not replace the original protected-header bytes used `EPP_DECRYPTION_KEY_PEM` accepts a private-key PEM alone or a PEM certificate bundle containing the private key, in plain text or base64 form. Guided setup issues a certificate inside Key Vault or -reuses an explicitly selected certificate version, pins its PEM backing-secret reference, and -keeps exactly one approved encryption credential on the application. Certificate synchronization +reuses a selected certificate version. It pins the PEM backing-secret reference and keeps exactly +one approved encryption credential on the application. Certificate synchronization is manual; Key Vault renewal does not automatically update Entra or select another decryption key. See the [certificate lifecycle](../setup/docs/README.md#encryption-certificate-lifecycle). @@ -439,10 +439,10 @@ stop polling/prevent late publication at shutdown. .NET has no poller to stop: f fills are not retained and later misses attempt retrieval. It does not proactively refresh a still-valid entry. -JavaScript bounds shared acquisition to 2.5 seconds and propagates cancellation through the SDK -transport. Python bounds waits and SDK connect/read inactivity to 2.5 seconds but cannot forcibly -cancel synchronous I/O. .NET uses a 2.5-second fetch budget linked to the fetch caller's cancellation -token. None is a whole-invocation deadline. +JavaScript limits shared acquisition to 2.5 seconds and passes cancellation through the SDK +transport. Python limits waits and SDK connect/read inactivity to 2.5 seconds. Synchronous I/O +may continue until the underlying call returns. .NET uses a 2.5-second fetch budget linked to +the caller's cancellation token. None is a whole-invocation deadline. All runtimes fetch a complete API-key/customer-ID bundle before use and use managed identity for vault access. With caching enabled, Soprano reuses managed-identity and client-assertion SDK credentials @@ -459,7 +459,7 @@ Do not log SDK exception details or tokens. No cache retries/deduplicates provid ## 5. Required behaviors -- **Fail-closed**: only `Continue` → `200 accepted`; unknown status → `Fail`. +- **Safe outcome mapping (fail-closed)**: only `Continue` → `200 accepted`; unknown status → `Fail`. - **Managed identity**: Key Vault access via managed identity only (user-assigned if `AZURE_CLIENT_ID` set, else system-assigned). No static credentials. - **Privacy**: never log phone numbers, passcodes, nonce values, bearer tokens, API keys, JWE headers/payloads, @@ -478,13 +478,13 @@ Do not log SDK exception details or tokens. No cache retries/deduplicates provid the authorized Microsoft caller application ID. No excluded path may bypass authentication for SendOtp. The trigger remains `authLevel: anonymous`; there is no application token validation or function-key fallback. The platform rejects unauthenticated requests with `401` and denies callers outside the - allowlist before the handler. **Never expose the endpoint to the public internet with Easy Auth off - or bypassed.** Core Tools supplies no Easy Auth: local execution must bind only to loopback, with - no tunnels or public forwarding. Neither request data, JWE decryption, a fixed nonce nor forwarded - principal headers authenticate the Entra ID caller. + allowlist before the handler. **Keep Easy Auth enabled for every public SendOtp route.** + Core Tools supplies no Easy Auth, so local execution stays on loopback without tunnels or public + forwarding. Request data, JWE decryption, a fixed nonce, and forwarded principal headers serve + other parts of the contract; Easy Auth authenticates the Entra ID caller. - **Timeout boundaries**: platform authentication and Key Vault retrieval happen outside the outbound HTTP timer. Python uses connect/read inactivity timeouts, not a hard elapsed-time deadline. The cap - therefore does not guarantee a 3.2-second end-to-end response, especially on cold starts. + does not guarantee a 3.2-second end-to-end response, especially on cold starts. A timed-out POST may already have been accepted; avoid blind retries that duplicate messages. ### Application logs @@ -545,8 +545,8 @@ delivery and provider-specific features need their own validation. standalone six-digit numeric runs and repeats the full message. Validate supported locales, passcode formats, and actual audio for the intended experience. - **Additional production capabilities:** add the body-size/content-type and E.164 validation, - subscription provisioning, certification, least-cost routing, or voice-callback workflows - required by the design. Native caller fallback remains with the caller rather than this Function. + subscription setup, certification, least-cost routing, or voice-callback workflows required + by the design. Native caller fallback remains with the caller rather than this Function. See [setup compatibility](ONBOARDING.md#setup-script-compatibility) for credential provisioning, endpoint format and unsupported setup options. diff --git a/docs/COST-CALCULATOR.md b/docs/COST-CALCULATOR.md index 439549c..0938387 100644 --- a/docs/COST-CALCULATOR.md +++ b/docs/COST-CALCULATOR.md @@ -45,11 +45,10 @@ approved folder. ## Generate the activity report -The following sample installs the required Microsoft Graph modules for the current user, -connects to Microsoft Graph, identifies SMS and voice authentication steps, and matches -each masked phone detail to a registered phone method. It caches phone-method lookups so -each user is queried only once. Replace `$start` and `$end` with the required period to -analyze. +The sample installs the required Microsoft Graph modules and connects to Microsoft Graph. +It finds SMS and voice authentication steps, matches each masked phone detail to a registered +phone method, and caches each user's phone-method lookup. Set `$start` and `$end` to the +required reporting period. ```powershell # 1. Install required modules if they are missing @@ -176,11 +175,10 @@ from that detail and matches the visible suffix (`90`) against the user's regist methods. It then extracts the calling code from the uniquely matched registered number, whose Graph format includes a space after the code, such as `+91 1234567890`. -For SMS, only `mobile` and `alternateMobile` registrations are considered; voice can use -any registered phone type. A row is grouped as `Unknown` when the masked detail exposes -fewer than two digits, no registered number matches, more than one registered number has -the same visible suffix, or the matched number has an unexpected format. This avoids -guessing a destination. Review the `Unknown` total before relying on the estimate. +For SMS, the script uses `mobile` and `alternateMobile` registrations. Voice can use any +registered phone type. The report uses `Unknown` when the visible detail is too short, +does not match one registered number, or has an unexpected format. This avoids guessing +a destination. Review the `Unknown` total before relying on the estimate. The script does not use sign-in geography. `-All` requests all available pages, but large tenants should still account for Microsoft Graph throttling and execution time. The script diff --git a/docs/FRONTDOOR.md b/docs/FRONTDOOR.md index 386e362..d8fd924 100644 --- a/docs/FRONTDOOR.md +++ b/docs/FRONTDOOR.md @@ -8,14 +8,14 @@ Azure Front Door provides **one public EPP URL backed by Function Apps in multiple regions**. Start with a validated [single-region deployment](GETTING-STARTED.md), then use this guide to plan and implement the regional origins, readiness route, Front Door configuration, validation, -failover drills, and rollback. Transition errors are part of the acceptance criteria. +failover drills, and rollback. Include transition errors in the acceptance checks. Follow [scope](#scope-and-observed-behavior), [architecture](#architecture), [plan/capacity](#1-plan-the-regional-deployment), [origins/keys](#2-prepare-equivalent-independently-provisioned-origins), [readiness](#3-provide-a-non-delivering-readiness-endpoint), [routing](#4-configure-front-door-manually), [validation](#5-validate-before-manually-activating-policy), and [fire-drill](#6-regional-failover-fire-drill). -Keep [historical results](#observed-failover-results-and-limitations) separate from -your acceptance criteria; use [operations/rollback](#operations-and-rollback) after validation. +Use [historical results](#observed-failover-results-and-limitations) as background for the +acceptance plan. After validation, follow [operations and rollback](#operations-and-rollback). ## Scope and observed behavior @@ -28,9 +28,9 @@ regional package and validate it before enabling probes. The released packages r single-region baseline; include runtime-specific validation for .NET or Python. The recorded trials cover encrypted evaluation, caller and origin restrictions, and controlled -Function stop/restart. The acceptance plan must cover the required features: approved live -SMS/voice checks, real Microsoft Entra caller behavior, regional dependency loss, custom domains, -WAF or Private Link, and production-representative load. +Function stop/restart. Build the acceptance plan around the features in use. It can include approved live SMS and voice +checks, real Microsoft Entra caller behavior, regional dependency loss, custom domains, WAF or +Private Link, and realistic load. ### Regional resiliency responsibility @@ -72,13 +72,12 @@ normally adds an **origin**, not another customer-facing endpoint. ### Two-region baseline to provision and validate -Use two independently provisioned regional stacks, A and B, at equal Front Door priority for -active-active service. Each has its own Function App and hosting capacity, private runtime/package -storage, Key Vault containing the shared decryption key and applicable provider credentials, -managed identities with local scoped access, and regional telemetry. Configure provider federation -for each regional outbound identity where required. Neither stack may require the other region's -storage, vault, or identity to serve or recover; verify startup, key resolution, and credential -refresh as well as already-warm requests. +Use two separate regional stacks, A and B, at equal Front Door priority for active-active service. +Each stack has its own Function App, hosting capacity, private package storage, Key Vault, managed +identities, and telemetry. Both vaults contain the matching decryption key and the provider +credentials needed in that region. Configure provider federation for each regional outbound +identity when required. Verify that each stack can start, resolve keys, refresh credentials, and +serve requests without the other region's storage, vault, or identity. Keep one shared Front Door public SendOtp URL and endpoint application registration, with identical issuer, audience, authorized-caller policy, selected provider route, and encryption public-key @@ -116,42 +115,38 @@ application configuration or certificate unexpectedly. ### Regional capacity -**EACH region must be provisioned and validated to serve the combined peak workload on its own.** -For a balanced two-region active-active baseline, this means **at least twice its normal per-region -traffic**, plus explicitly planned headroom for bursts, retries, probe traffic, and dependency -overhead. Reserve that capacity in both A and B before relying on regional failover; spare capacity -only in one direction is insufficient. Planning retry headroom does not authorize blind retries -of live sends. +**Size and validate each region to serve the full combined peak workload.** In a balanced +two-region active-active design, each region needs capacity for **at least twice its normal +traffic**. Add room for bursts, approved retries, probes, and dependencies. Reserve this +capacity in both A and B before using regional failover. Retry capacity does not change the rule +to check an uncertain live send before retrying. -Use the expected peak offered workload, not a quiet-period average or the lower throughput left -after requests time out. Equal weights do not guarantee equal routing. If normal traffic is -unequal, each region still needs the entire combined peak workload plus headroom; the less-used -region may need more than twice its usual traffic. An N-region alternative needs a documented -failure budget and measured capacity for every permitted survivor set and routing distribution. -Adding origins does not waive the double-load requirement for the balanced two-region baseline. +Use the expected peak workload rather than a quiet-period average. Equal weights do not guarantee +equal routing. If traffic is uneven, each region still needs the combined peak plus headroom, so +the less-used region may need more than twice its usual traffic. For more than two regions, +document the failure budget and measure capacity for every allowed survivor group and routing +pattern. Record the workload mix, offered and completed request rates, concurrency, payload sizes, test duration, per-region latency percentiles, errors/timeouts, resource saturation, and dependency throttling. Verify hosting/subscription quotas, available warm capacity and scale-out delay, storage and vault limits, identity/token-service capacity, and provider account/rate limits. -Autoscale maxima, provisioned instance counts, and approved quotas alone are **not capacity proof**. -Pass only against agreed latency/error and sustained-load criteria with measured evidence in -both directions. No measured production-capacity result is supplied by this guide. +Use measured load results in both failover directions. Autoscale limits, instance counts, and +approved quotas are inputs, while sustained request rate, latency, errors, and saturation show +whether the design meets the agreed capacity target. This guide does not include a measured +production result. Encrypted evaluation requests bypass request-path provider credential resolution and outbound provider calls. A double-load evaluation drill proves only the exercised ingress, authentication, decryption, and routing path, **not complete live-send capacity**. Background credential refresh can still run independently; its presence does not prove live-path credential capacity. -The capacity gate separately requires production-representative evidence in both survivor -regions using a **non-delivering, provider-approved sandbox/stub** that exercises the live request -path, credential acquisition/refresh, and representative provider latency and quota/throttling -behavior, or separately authorized delivery validation. Document how the sandbox/stub represents -production and obtain provider capacity/quota evidence for what it cannot reproduce; an instant -success stub or bypassed credential lookup is insufficient. Evaluate cold starts, cache expiry, -and credential/key refresh so warm caches do not hide a dependency on the failed region. Keep -this separate from the evaluation-only fire-drill; do not silently enable live sends in that drill -or mark the full capacity gate passed using evaluation-only throughput. +For full-path capacity evidence, test both survivor regions with a **non-delivering, +provider-approved sandbox or stub**. Exercise the live request path, credential refresh, realistic +provider delay, and quota or throttling behavior. If the test system cannot match part of +production, record the difference and add provider capacity evidence. Include cold starts, cache +expiry, and key refresh so warm caches do not hide a regional dependency. Keep this test separate +from the evaluation-only fire drill, and use live sends only through a separately approved test. ## 2. Prepare regional origins @@ -181,7 +176,7 @@ Keep public access disabled while setting up authentication, keys, identities, a ### Coordinate the encryption key -The caller must be able to encrypt once for any eligible origin. Every origin therefore needs the +The caller must be able to encrypt once for any eligible origin. Every origin needs the **same RSA private key corresponding to the registered public certificate**. Front Door's HTTPS certificate is separate from this JWE encryption certificate. @@ -218,16 +213,17 @@ Implement a separate route such as **`/api/health/ready`** with this contract: Never exempt `/api/SendOtp`, `/api/*`, or the whole application. - Keep the origin-level Front Door access restrictions described below on the readiness route too. -Readiness proves neither provider availability nor handset delivery. Validate those separately. -Do not point probes at a nonexistent handler, use a cached static `200`, or disable Easy Auth to -make probes succeed. Front Door treats only a `200` probe response as healthy. +Readiness confirms that the handler and decryption key are usable. Validate provider availability +and handset delivery separately. Point probes to the implemented readiness handler, return a live +result rather than a cached static `200`, and keep Easy Auth enabled for SendOtp. Front Door treats +only a `200` probe response as healthy. If every origin is unhealthy, Front Door can still route requests across them. Keep authentication and application failure handling effective independently of probe status. ## 4. Configure Front Door manually In the Azure portal, create a Front Door Standard/Premium profile, an endpoint, and an origin group. -These are the **settings we tested**. Treat them as a starting point, not a performance guarantee: +These are the **tested starting settings**. Validate them against the required performance target: | Setting | Tested value | |---|---| @@ -357,20 +353,18 @@ instead of treating the observed timings below as an SLA. Mark each criterion pass/fail/not tested with an owner and retest date. Never attach secrets, bearer tokens, request bodies, OTPs, or nonces. -### What this drill does not prove + +### Additional validation -Stopping a Function App simulates one application origin becoming unavailable. It does **not** -simulate the loss of an Azure region's storage, vault, identity access, network paths, control -plane, or telemetry, nor prove the surviving stack can start or refresh credentials without them. -The regional-resiliency validation plan must separately validate loss of regional dependencies and telemetry, -using approved nonproduction fault scenarios and capturing evidence outside the affected region. -Include dependency refresh/startup and restoration behavior; a warm app-stop success alone leaves -these regional-outage gaps open. Record any scenario that cannot be safely exercised as an -unvalidated limitation requiring deployment-owner review, not a passing regional-outage test. +Stopping a Function App tests routing when one application origin is unavailable. Add separate +nonproduction tests for regional storage, vault, identity access, network paths, control plane, +and telemetry. Capture evidence outside the affected region. Include cold startup, credential +refresh, and restoration so the plan covers more than an already-warm app. Record any scenario +that cannot be tested safely and assign an owner and follow-up date. -Evaluation also does not prove real Entra caller deadlines/retries, provider acceptance, or handset -delivery. Validate those separately with the provider and onboarding owner. Neither this runbook -nor Front Door makes retries automatically safe or guarantees zero downtime. +Use separate approved checks for real Microsoft Entra caller timing, provider acceptance, and +handset delivery. Define retry and availability targets with the provider and onboarding owner. +Front Door and this runbook do not guarantee zero downtime or make uncertain retries safe. ## Observed failover results and limitations diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index 1f0b02f..a0884b3 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -16,15 +16,15 @@ Portal screenshots hide identifying details; labels and layout may differ. ## 1. Get ready -Create an **isolated test environment** in a nonproduction tenant and subscription. Use a new -application registration and a unique resource prefix so the endpoint is easy to identify and -remains separate from production and other deployments. +Start with a **separate test environment** in a nonproduction tenant and subscription. Create a +new application registration and choose a unique resource prefix. This makes the endpoint easy +to identify and keeps testing separate from production. Before purchasing or deploying, confirm access to a supported provider's EPP integration through [Microsoft Security Store](https://securitystore.microsoft.com/private-solutions). Arrange Microsoft's approved testing and activation procedure with your tenant administrator. -Buying an offer is one part of onboarding; tenant access, the approved procedure, and deployment -are completed separately. Confirm those paths before continuing, or use the +The complete onboarding path includes the provider offer, tenant access, the approved procedure, +and deployment. Confirm each part before continuing, or use the [access and support guidance](ONBOARDING.md#before-purchasing-or-deploying) to get them in place. In Security Store, open **Private solutions** and select your approved subscription. @@ -79,8 +79,8 @@ Use these field labels to find the right IDs: *Use **Application (client) ID** for `-ApplicationId` and **Directory (tenant) ID** for `-TenantId`. The **Object ID** is not either of these setup inputs. All ID values are hidden in this example.* -Do not add a client secret, API permission, or app role yourself; setup handles the remaining -configuration. See [application registration details](../setup/docs/README.md#step-1---manually-create-the-application). +Let setup add the required API permission and app role. No client secret is needed. +See [application registration details](../setup/docs/README.md#step-1---manually-create-the-application). Also keep your Azure subscription ID handy. The [setup values worksheet](ONBOARDING.md#inputs-you-supply-to-setup) explains where to find @@ -127,10 +127,10 @@ to reuse one exact Key Vault certificate version or approve replacement. Coordin first: changing these credentials affects every endpoint sharing the application. **Review the complete deployment plan before typing `Yes`.** Check the accounts, region, -SMS and voice provider URLs, platform, plan, cache settings, resource names, permissions, and -the exact certificate key IDs to keep or remove and any access to an existing vault. Setup grants the -Microsoft phone-provider service principal Microsoft Graph `Application.Read.All`, a tenant-wide -application-read permission. `No` or Enter cancels deployment; sign-in and prerequisite prompts +SMS and voice provider URLs, platform, plan, cache settings, and resource names. Also check +permissions, certificate key IDs, and access to any existing vault. Setup grants the Microsoft +phone-provider service principal Microsoft Graph `Application.Read.All`, a tenant-wide +application-read permission. `No` or Enter cancels deployment. Sign-in and prerequisite prompts are separate from this approval. **Plan review without deployment:** run interactively and choose **No** at the final prompt. @@ -138,15 +138,15 @@ This still performs online preflight and downloads. There is no `-DryRun` or `-W see [plan review](../setup/docs/README.md#reviewing-the-plan-without-deploying) for details. Wait for **Deployment completed** and the saved summary before Step 4. Setup creates the -Function App, storage, Key Vault, identities, and monitoring; **do not create them manually first**. +Function App, storage, Key Vault, identities, and monitoring as one coordinated deployment. +Begin without precreating these resources so setup can apply the complete reviewed plan. Keep `epp-output` private. Its public certificate and [summary](../setup/docs/README.md#read-the-deployment-summary) identify resources, endpoint, application client ID, and encryption key ID. No private key is downloaded. Setup verifies that the Function can resolve its selected private-key reference before changing -the registered encryption certificates. It also verifies **Easy Auth**, the endpoint's -caller-authentication protection, before opening public access. Never disable it to fix a setup -or testing problem. If setup fails, use the +the registered encryption certificates. It also verifies **Easy Auth**, the endpoint's caller-authentication protection, before opening +public access. Keep Easy Auth enabled during setup, testing, and operation. If setup fails, use the [troubleshooting guide](../setup/docs/Troubleshooting.md) rather than deleting resources and starting over. To find the deployed resources, open **Azure portal > Resource groups** and select the group @@ -160,8 +160,8 @@ the portal's Create button to deploy the endpoint.* ## 4. Connect your provider and test -Complete [provider authentication](ONBOARDING.md#complete-provider-authentication); deployment -does not do this for you. API-key integrations use the +Azure deployment is complete. Next, finish +[provider authentication](ONBOARDING.md#complete-provider-authentication). API-key integrations use the [setup-created vault and exact secret names](ONBOARDING.md#where-to-enter-api-key-credentials-in-key-vault). OAuth uses the provider-administrator handoff, not API-key secrets. Keep credentials out of source, screenshots, and shared logs. diff --git a/docs/LOGGING.md b/docs/LOGGING.md index 71eda28..19dc7bd 100644 --- a/docs/LOGGING.md +++ b/docs/LOGGING.md @@ -2,12 +2,12 @@ [Documentation](README.md) | [Find deployed logs](MONITORING.md#runtime-specific-log-discovery) | [HTTP contract](CONTRACT.md) -This reference defines endpoint events and their interpretation. Incident investigation begins -with the monitoring walkthrough rather than a search for a universal JSON request summary. +This reference explains endpoint events and how to read them. Start an investigation with the +monitoring walkthrough, then use the runtime format shown here. -**Never log request bodies, decrypted fields, credentials, nonces, raw exceptions, or provider -response bodies.** Keep SDK, platform, and proxy body tracing off. This page describes what the -application emits; your monitoring pipeline determines what is collected. +**Keep request bodies, decrypted fields, credentials, nonces, raw exceptions, and provider +response bodies out of logs.** Keep SDK, platform, and proxy body tracing off. This page +describes what the application emits; the monitoring pipeline determines what is collected. ## Runtime formats @@ -17,7 +17,8 @@ application emits; your monitoring pipeline determines what is collected. | Python | Standard `logging`; `otp_log.py` defines fixed event IDs, names, levels, and fields. An immutable `LoggerAdapter` supplies Function and Microsoft trace identifiers. | | .NET | Standard `ILogger`; `OtpLog` defines source-generated IDs/names. `ILogger.BeginScope` supplies `FunctionName`, `FunctionRequestId`, `FunctionInvocationId`, `MsClientRequestId`, `MsCorrelationId`, and `MsCorrelationIdSource`. | -Each invocation ends with a fixed `request_completed` event, not a mutable comprehensive summary. +Each invocation ends with a fixed `request_completed` event. It is not a changing summary of +every earlier event. The configured logger/exporter owns formatting and property preservation. The fields below describe JavaScript, **not portable custom properties**. Python uses `event_name` and extras such as `httpStatus`; .NET uses `HttpStatus` and scopes such as `FunctionRequestId`. diff --git a/docs/MONITORING.md b/docs/MONITORING.md index dee5465..ac78f80 100644 --- a/docs/MONITORING.md +++ b/docs/MONITORING.md @@ -22,9 +22,9 @@ For a first single-region deployment: reminders and cost ownership. For **missing data**, use [collection checks](#9-sampling-ingestion-gaps-privacy-and-validation). -Optional: [multiple regions](#3-multiple-regions-and-cross-workspace-queries), +Additional guidance covers [multiple regions](#3-multiple-regions-and-cross-workspace-queries), [Workbook](#6-build-a-shared-operations-workbook), [availability checks](#7-availability-authenticated-functions-and-fc1), -and [Front Door](#8-optional-azure-front-door-layer). None is needed to find single-region logs. +and [Front Door](#8-optional-azure-front-door-layer). Start with the single-region steps above. These examples do not create monitors automatically. Review permissions, privacy, and charges before applying them. Thresholds and intervals are **starting points, not SLAs**; tune them to @@ -44,8 +44,8 @@ Keep a private inventory with one row per expected regional endpoint: | Expected traffic or authorized synthetic-test cadence | Decide whether missing data is actionable, including standby origins. | | Front Door profile, endpoint, origin group, and origin | Associate regional routing and health with the Function. | -Do not derive the expected inventory solely from recent telemetry: an origin that -never reports would be absent from that list. +Build the expected inventory from the deployment record. Recent telemetry alone can miss an +origin that has not reported. [Guided setup](../setup/docs/README.md) creates the collection resources, not action groups, Workbooks, or alerts. [Verify the deployed collection configuration](APPLICATION-INSIGHTS.md#single-region-configuration); @@ -56,8 +56,8 @@ The template requests 30-day retention; check effective workspace/table retentio ### Portal resources 1. Open the Function App named in your deployment summary. -2. Select **Monitoring > Application Insights**. Follow the linked resource name; do not - select **Change your resource** or **Apply** just to view your logs. +2. Select **Monitoring > Application Insights** and follow the linked resource name. + Viewing logs does not require **Change your resource** or **Apply**. ![Function App Monitoring menu with Application Insights selected and its connected resource link](images/onboarding/monitoring-link.png) @@ -611,11 +611,11 @@ separate readiness design described above. See [Front Door monitoring][front-doo ## 9. Sampling and data quality -If data stops arriving, check the time range and workspace/component/role filters, -actual traffic, deployment changes, log levels, host/worker exporter configuration, -ingestion authorization/network access, sampling, throttling, and workspace daily -caps. Retention and collection transformations can also affect query results. -Never interpret an inaccessible workspace or missing table as a healthy empty window. +If data stops arriving, first check the time range, workspace, component, role filters, +and actual traffic. Then check recent deployments, log levels, host and worker exporters, +ingestion access, network access, sampling, throttling, and workspace daily caps. +Retention and collection transformations can also affect query results. Treat an +inaccessible workspace or missing table as a collection issue, not a healthy empty window. This query estimates the event-to-ingestion gap for **received** requests: diff --git a/docs/MULTI-PROVIDER-IMPLEMENTATION.md b/docs/MULTI-PROVIDER-IMPLEMENTATION.md index 8f2fcda..ca94dbf 100644 --- a/docs/MULTI-PROVIDER-IMPLEMENTATION.md +++ b/docs/MULTI-PROVIDER-IMPLEMENTATION.md @@ -4,20 +4,20 @@ [Documentation](README.md) | [Normal deployment](GETTING-STARTED.md) | [Provider interface](BRING-YOUR-OWN-PROVIDER.md) -This guide describes a customer-owned primary-to-secondary provider design. It adds code, -durable state, provider-specific approval, and dedicated testing around the existing -`/api/SendOtp` URL. Keep the provider order on the server for one SMS channel rather than letting -the caller select accounts or treating the order as a vendor ranking. +This guide shows how to add controlled primary-to-secondary provider fallback around the existing +`/api/SendOtp` URL. The design adds durable state, provider-specific approval, and dedicated +tests. Keep the provider order on the server for one SMS channel. -Try a secondary **only with proof that the primary did not accept**. Timeouts, lost responses, -and generic errors are not that proof; never automatically resend on those signals. +Use the secondary **only after confirmed proof that the primary did not accept the request**. +Treat timeouts, lost responses, and generic errors as uncertain outcomes and investigate them +without an automatic resend. Follow [policy](#1-set-a-fixed-provider-order-and-a-deny-by-default-policy), [account configuration](#2-configure-and-prevalidate-both-accounts), [credential isolation](#3-give-each-context-its-own-credential-service-and-lifecycle), [durable state](#4-add-durable-operation-state-before-enabling-fallback), -[orchestration](#5-integrate-one-guarded-fallback-into-the-handler), -[deadline](#6-enforce-one-aggregate-deadline), and [validation](#7-validate-offline-then-deploy-only-after-separate-approval). +[fallback flow](#5-integrate-one-guarded-fallback-into-the-handler), +[end-to-end deadline](#6-enforce-one-aggregate-deadline), and [validation](#7-validate-offline-then-deploy-only-after-separate-approval). [.NET/Python pointers](#net-and-python-integration-pointers) are not implemented fallback. @@ -49,10 +49,10 @@ Keep **routing order** separate from **fallback eligibility**. Use this policy: | Primary-only credential acquisition fails before transport is entered | Eligible only if this pre-dispatch fact is recorded, the account policy explicitly permits it, the durable operation guard allows the transition, and the request budget is sufficient. | | Provider-specific, documented proof that the primary did not accept the operation | Eligible only through a reviewed nonacceptance classifier, explicit account policy, the durable guard and sufficient budget. | -**Default to no fallback.** No real provider response is designated safe to fall back -from by this guide. Obtain and review provider-specific semantics before enabling that path. -An expired/rejected inbound token never reaches it. A provider credential failure must not bypass -account suspension, consent requirements, entitlement restrictions or a provider block. +**Keep fallback off until the provider confirms a safe nonacceptance signal.** Review that +provider-specific signal and the account policy before enabling the path. An expired or rejected +inbound token never reaches fallback. Provider credential handling continues to honor account +status, consent, access rules, and provider blocks. ## 2. Configure and prevalidate both accounts @@ -94,9 +94,9 @@ This SMS-only policy requires `EPP_PROVIDER_SMS_ENDPOINT` for each account. A vo `EPP_PROVIDER_VOICE_ENDPOINT` instead. The validated request channel selects the matching endpoint; there is no shared or other-channel endpoint fallback. -Your startup loader must reject duplicate/missing IDs, missing endpoints for the policy's channel, -mismatched authentication modes, unapproved endpoints, missing credential references and invalid -deadline policy. Validate both contexts before enabling the endpoint, not only after the primary fails. +At startup, validate both account configurations before enabling the endpoint. Check for unique +IDs, the required channel endpoint, matching authentication mode, approved URLs, credential +references, and a valid deadline policy. For each selected [adapter](../javascript/src/functions/providers), inspect `credentialSpec` for the required secret names and any account identifier, and `createRequest` for endpoint and @@ -108,8 +108,8 @@ Set an account/destination-approved sender where required; an adapter's default proof of approval. These requirements apply equally to primary and secondary roles. Store no credential values in the configuration. Attach each referenced user-assigned managed -identity to the Function App and grant the vault-reading identity scoped secret-read access, -such as Key Vault Secrets User under RBAC or the approved access-policy equivalent. If using +identity to the Function App. Grant the vault-reading identity scoped secret-read access, such +as Key Vault Secrets User under RBAC or the approved access-policy equivalent. If using the system-assigned identity, grant that identity instead. Client-ID settings do not attach identities or grant access. Complete provider account, recipient, sender and consent prerequisites; see [provider onboarding](ONBOARDING.md#complete-provider-authentication). @@ -171,11 +171,10 @@ changes; never repurpose a live service for another account. ## 4. Operation state -The repository has **no durable attempt store or idempotency contract**. Implement these before -adding a second possible submission. Agree on a stable, authorized logical-operation key with -the caller/test contract. Do not assume a generated invocation ID, correlation GUID or nonce is -an idempotency key. Bind the key to the authorized caller, operation and immutable request/policy -identity so another request cannot reuse it to obtain a different delivery. +Add a **durable attempt store and idempotency contract** before allowing a second submission. +Agree on a stable, authorized operation key with the caller and test owner. Generated invocation +IDs, correlation GUIDs, and nonces serve other purposes. Bind the operation key to the authorized +caller, request, and fixed policy so another request cannot reuse it for a different delivery. Use transactional/conditional writes in a durable shared store, not an in-memory map or lock. Atomically claim an operation across concurrent invocations and worker restarts. Persist intent @@ -205,7 +204,8 @@ Entra ID retries or native fallback outside this endpoint can still produce dupl behavior with the owner rather than promising the local guard prevents it. -## 5. Fallback orchestration + +## 5. Fallback flow In [SendOtp.js](../javascript/src/functions/SendOtp.js), preserve authentication at the platform boundary, [envelope validation](../javascript/src/functions/entraPayload.js), @@ -221,7 +221,7 @@ returns a structured category. Use `providerConfig` for outbound settings and th [OtpDelivery](../javascript/src/functions/delivery.js) construction and content handling intact. The existing credential catch immediately returns 502, and transport/result failures immediately -return errors. Your attempt helper must report the stage to the orchestrator instead of hiding +return errors. The attempt helper reports the stage to the main flow instead of hiding it in a catch that calls the secondary. Preserve their final HTTP classifications when fallback is denied. This is **policy pseudocode**, not executable glue or a complete handler: @@ -264,9 +264,9 @@ reinterpret generic `Fail` or a broad status range as proof. [providerTransport.js](../javascript/src/functions/providerTransport.js) collapses fetch and response-body failures into `provider_timeout`/`provider_network_error`. Those errors do **not** expose whether the request was transmitted. Treat them as uncertain after intent, including -connection errors; do not guess they happened before send. A primary credential failure can -be pre-dispatch only when control flow and operation state prove transport was never entered, -the failure is isolated to that primary, and account policy still permits the secondary. +connection errors; do not guess they happened before send. Treat a primary credential failure +as pre-dispatch only when the flow and operation state prove transport was never entered. +The failure must also be isolated to the primary, and account policy must still allow the secondary. Keep endpoint allowlists, HTTPS checks, manual redirects and per-provider response mapping. Never forward inbound authorization. Preserve the endpoint success nonce/correlation response @@ -274,20 +274,21 @@ only for evaluation or an accepted delivery result; terminal/uncertain outcomes nonce. Continue using [safe logging](../javascript/src/functions/logging.js), with an explicit per-attempt provider ID/classification and one final request outcome. -## 6. Enforce one aggregate deadline + +## 6. Use one end-to-end deadline -Define an end-to-end budget with the service/caller owner; this guide provides no SLA or magic -timeout value. Include validation, durable-store waits, primary credential acquisition/transport, -secondary credential acquisition/transport and final response work. An operation must not receive -a fresh budget when a duplicate invocation arrives. +Define one end-to-end time budget with the service and caller owners. Include validation, +durable-store waits, both providers' credential and transport work, and the final response. +A duplicate invocation continues to use the original operation budget. The current credential service has its own acquisition bound and the transport its own timeout. `parseProviderTimeout` normalizes a single provider timeout, **not** an aggregate deadline. Add explicit remaining-budget checks and cancellation propagation in your customer integration. Do not use `Promise.race` to return while a send continues in the background. If ownership, deadline or cancellation changes after dispatch, record uncertainty and never fall back. -Reserve enough time for the whole secondary attempt and finalization, rechecking before intent; -insufficient budget means no secondary even when nonacceptance is otherwise eligible. +Reserve enough time for the complete secondary attempt and final response, and check the remaining +time again before sending. Skip the secondary when the remaining time cannot cover that work, +even when the primary result would otherwise allow fallback. ## 7. Validation and deployment diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index 8e5c066..0cd81fa 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -28,17 +28,16 @@ a missing adapter. labels, pricing, and access can change. A store listing does not mean the setup script supports that integration; check [guided provider support](#provider-credential-names).* -Have your tenant administrator confirm the supported EPP onboarding/activation procedure with -Microsoft and the provider **before incurring deployment costs**. This repository does not define -tenant eligibility, licensing, preview enrollment, or a self-service activation entitlement. -The allowed-tenants preview used by setup must also be available in the customer tenant. If an -offer, preview, authorized test caller, or activation procedure is unavailable, stop at that gate -and use the offer's available support route or your organization's Microsoft support channel. -Do not assume that being an Azure subscription owner grants access to these features. - -Assign an **onboarding owner** in your organization to coordinate tenant/policy, Azure, and -provider administrators with Microsoft support. Obtain approved test and policy-change procedures -before deployment; the repository supplies no named onboarding contact. +Before creating deployment costs, have the tenant administrator confirm the supported EPP +onboarding and activation procedure with Microsoft and the provider. Confirm tenant eligibility, +licensing, preview enrollment, the allowed-tenants preview, an authorized test caller, and the +activation path. Use the offer's support route or the organization's Microsoft support channel +to complete any missing step. Azure subscription ownership is separate from access to these +features. + +Assign an **onboarding owner** to coordinate tenant policy, Azure, provider administrators, and +Microsoft support. Get the approved test and policy-change procedures before deployment. Use the +organization's existing support contacts because this repository does not list a named contact. Use the [production-readiness considerations](CONTRACT.md#production-limitations) to plan the capabilities your live service needs, such as durable delivery, retry and deduplication strategy, @@ -119,9 +118,8 @@ certificate is separate again. Fix the failing path rather than replacing unrela ## Complete provider authentication -Guided setup currently supports **Soprano and Telesign only**. The other bundled adapters are -developer integrations, not additional guided provider offers. The table is alphabetical, -not a recommendation order: +Guided setup supports **Soprano and Telesign**. The other bundled adapters are available for +custom development and deployment. The table is alphabetical, not a recommendation order: | Provider | Credential names / mode | Deployment support | |---|---|---| @@ -192,18 +190,22 @@ where the provider supports it, and verify live delivery before retiring them. Setup creates customer-side federation, **not** the commercial account or access in Soprano's tenant. Complete this handoff before treating OAuth as ready. -Send the following **identifiers only** through the provider's approved onboarding channel: -customer tenant ID; `applicationId` / `EPP_OUTBOUND_CLIENT_ID`; intended channels and their full provider URLs; -`EPP_PROVIDER_TENANT_ID`, `EPP_PROVIDER_APP_ID`, and `EPP_PROVIDER_SCOPE`; and the account/sender -reference requested by the provider. If troubleshooting federation, also supply the outbound -identity Client ID and Principal ID, and the federated credential's issuer/subject/audience -metadata. Never supply the managed-identity assertion, access token, private key, or a new client secret. +Send **identifiers only** through the provider's approved onboarding channel: + +- customer tenant ID and `applicationId` / `EPP_OUTBOUND_CLIENT_ID` +- intended channels and their full provider URLs +- `EPP_PROVIDER_TENANT_ID`, `EPP_PROVIDER_APP_ID`, and `EPP_PROVIDER_SCOPE` +- the provider-requested account or sender reference + +For federation troubleshooting, also provide the outbound identity Client ID and Principal ID, +plus the federated credential's issuer, subject, and audience. Keep the managed-identity assertion, +access token, private key, and client secrets private. -Ask the provider administrator to confirm the API tenant/app/scope, provision/authorize the -calling application's service principal in that tenant through its supported process, and -confirm the exact consent/application roles and account/channel entitlement. This repository -does not publish a supported Soprano consent URL or role-ID catalog; do not guess a role or -construct an admin-consent link from the sample's GUIDs. Record their completion confirmation. +Ask the provider administrator to confirm the API tenant, app, and scope. They can then authorize +the calling application's service principal through the provider's supported process and confirm +the exact consent, application roles, and account/channel access. Use the provider's approved +consent path rather than building a URL or role list from sample GUIDs. Record the completion +confirmation. Inspect the dedicated app's **Certificates & secrets > Federated credentials** and the outbound identity's Overview. Setup's subject is the identity's **Principal ID**, issuer is the customer's @@ -215,17 +217,17 @@ for the mechanism, then use an authorized controlled live test to verify the ful ## Validate the deployed endpoint -**Authorized testing is an assisted gate.** This repository supplies offline runtime tests, not -a customer self-service tool that obtains a token as Microsoft's phone-provider application. -An ordinary `az account get-access-token` result or a customer's test app is not that caller. -Do not add a test application to `allowedApplications`, disable Easy Auth, or publish a local -unauthenticated host to work around this. Ask the onboarding owner to arrange Microsoft's -approved EPP caller/test procedure. If unavailable, record **not validated** and do not activate. - -Provide that operator the endpoint URL, customer tenant ID, endpoint application client ID, -identifier URI, encryption key ID, **public** certificate/thumbprint, and provider from your -inventory/summary, plus the intended channels and approved test window. Agree on controlled -recipients privately and obtain explicit permission before any paid/live send. +**Use Microsoft's assisted process for authorized testing.** The repository provides offline +runtime tests, while Microsoft supplies the approved EPP caller and test procedure. An ordinary +`az account get-access-token` result or a customer test app represents a different caller. +Keep `allowedApplications` and Easy Auth unchanged, and keep local hosts private. Ask the +onboarding owner to arrange the approved procedure. Activate after that validation is complete. +If the procedure is not available, record **not validated** and leave the policy inactive. + +Provide the operator with the endpoint URL, customer tenant ID, endpoint application client ID, +identifier URI, encryption key ID, and **public** certificate or thumbprint. Also provide the +selected provider, intended channels, and approved test window from the deployment inventory. +Agree on controlled recipients privately and get permission before any paid live send. | Order / owner | Check | Evidence to retain without sensitive payloads | |---|---|---| diff --git a/docs/README.md b/docs/README.md index ee533a8..98a853f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,9 +2,9 @@ [Sample overview and architecture](../README.md) -**First deployment:** follow [Get started](GETTING-STARTED.md). Before spending or deploying, -confirm supported access and Microsoft's approved testing and activation process. Build the first -endpoint in an isolated nonproduction environment, validate it, and then complete policy activation. +**First deployment:** follow [Get started](GETTING-STARTED.md). Confirm supported access and +Microsoft's approved testing and activation process first. Build the endpoint in a separate +nonproduction environment, validate it, and then complete policy activation. ## Table of Contents diff --git a/docs/SIGN-IN-MONITORING.md b/docs/SIGN-IN-MONITORING.md index 918929e..128ff10 100644 --- a/docs/SIGN-IN-MONITORING.md +++ b/docs/SIGN-IN-MONITORING.md @@ -18,8 +18,9 @@ the export and alerts after endpoint setup. ## Recommended monitors -Agree source tenant, exact identity/resource IDs, and notification owners, then implement in -priority order. Do not scope by name fragments or every tenant application. +Agree on the source tenant, exact identity and resource IDs, and notification owners. Then +implement the monitors in priority order. Use exact IDs instead of name fragments or every +tenant application. ### Integration sign-in failures @@ -72,7 +73,8 @@ priority order. Do not scope by name fragments or every tenant application. **Set up once, then adapt:** select the [required categories](#choose-additional-export-categories), verify [fields](#field-checks-for-the-recommended-monitors), and reuse the [alert setup](#5-create-your-first-alert) and [safe test](#6-test-then-hand-over). -Choose each monitor's threshold from representative traffic, not a small export or a universal SLA. +Choose each monitor's threshold from normal traffic for that environment rather than a small +export or a universal SLA. ## 1. Choose your tenant and access diff --git a/dotnet/README.md b/dotnet/README.md index d974925..3591d85 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -20,16 +20,16 @@ the adapters in this folder. 1. Create the [local settings below](#environment-configuration). For live integration, choose an adapter's fixed `Name` from [Providers/](Src/Providers/), configure its endpoints/options, and complete [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). - No provider is selected by default. + Select one provider explicitly. 2. Build [dotnet.csproj](dotnet.csproj), run [xUnit tests](tests/Epp.Otp.Tests.csproj), and start Core Tools here, **on loopback only, without tunnels**; it has no Easy Auth. 3. For manual deployment, publish this app to a compatible .NET isolated Function App. Inspect the output and [.funcignore](.funcignore); the project excludes local settings, and private keys must not be packaged. Complete the [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist) before public - access: it is the only caller-authentication gate; never disable it or exempt SendOtp. - Run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint); - offline tests do not verify platform authentication. + access. Keep Easy Auth enabled for SendOtp, then run + [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint) to verify + platform authentication. **Regional continuity:** use the [Front Door guide](../docs/FRONTDOOR.md) to implement and validate the readiness route and regional deployment for .NET. The recorded trials used JavaScript, so @@ -55,8 +55,9 @@ For local evaluation, start Azurite and replace the test-key placeholder in this For live delivery, use the [shared settings table](../TECHNICAL.md#configure-environment-variables) for `EPP_PROVIDER_NAME`, both channel endpoints, authentication, and adapter-specific options. Keep values as strings, including `EPP_PROVIDER_TIMEOUT_MS`, for example `"1500"`. -Replace placeholders; credentials belong in the adapter's Key Vault secrets, not this file. -A missing/invalid selected channel endpoint fails closed without fallback. +Replace placeholders and store credentials in the adapter's Key Vault secrets. Each selected +channel requires a valid endpoint; an invalid or missing endpoint returns an error without using +the other channel. Core Tools loads `Values` into environment variables; direct worker execution and unit tests do not load local settings. [AppConfig.Read](Src/AppConfig.cs) reads them through `IEnv`. Restart after @@ -69,8 +70,9 @@ Provider credentials use managed identity, not a developer CLI login. Keep local ## Request behavior `POST /api/SendOtp` follows the [shared request/response contract](../docs/CONTRACT.md#1-http-api). -Easy Auth authenticates; JWE decryption, nonce values, and request fields do not. -The handler never parses or echoes incoming `Authorization`. +Easy Auth authenticates the caller. JWE decryption, nonce values, and request fields serve other +parts of the contract. The handler leaves incoming `Authorization` processing to Easy Auth and +does not echo it. **.NET binding runs first:** `[FromBody]` creates `EntraSendOtpPayload`. Malformed JSON, invalid enum tokens, or other deserialization errors can fail before `SendOtp`, its `ILogger` events, @@ -89,7 +91,8 @@ and [Telesign integration](../TECHNICAL.md#telesign-epp). Use the provider-neutral [BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the Microsoft Entra ID payload, -caller authentication, and response rules. The customer owns the outbound service integration. +caller authentication, and response rules. Customers can connect the outbound phone service +that meets their requirements. For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and [runtime-specific log discovery](../docs/MONITORING.md#runtime-specific-log-discovery). @@ -126,6 +129,6 @@ name used by the simple `SelectProvider` policy in `SendOtp`. Providers own auth credential resolution, request construction and private typed response DTOs. Their public `SendOtpAsync` method delegates to the base `SendJsonAsync` transport and maps the typed response into `ProviderResult`; provider mapping code never handles `HttpResponseMessage` or raw JSON DOM types. -Provider-specific response semantics stay in each provider; no shared response-mapping -registry is used. Common async HTTP transport lives in `PhoneProviderBase`. See +Each provider keeps its own response rules. Common async HTTP transport lives in +`PhoneProviderBase`. See [production-readiness considerations](../docs/CONTRACT.md#production-limitations) when planning a live service. diff --git a/javascript/README.md b/javascript/README.md index f6ce7de..777d3c2 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -20,15 +20,15 @@ the adapters in this folder. 1. Create the [local settings below](#environment-configuration). For live integration, choose a fixed provider ID from [providers/](src/functions/providers/), configure its endpoints/options, and complete [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). - No provider is selected by default. + Select one provider explicitly. 2. Install [package.json](package.json) dependencies, run its offline test script, and start Core Tools from this folder. **Bind only to loopback, without tunnels:** Core Tools has no Easy Auth. 3. For manual deployment, [package](../TECHNICAL.md#build-zips-locally) this app only, preserving dependencies and [.funcignore](.funcignore). Inspect for secrets before upload. Complete the - [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist) before public access: - it is the only caller-authentication gate; never disable it or exempt SendOtp. - Then run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint); - offline tests do not verify platform authentication. + [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist) before public + access. Keep Easy Auth enabled for SendOtp, then run + [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint) to verify + platform authentication. **Regional continuity:** [Front Door](../docs/FRONTDOOR.md) uses multiple regional origins behind one URL. Its JavaScript trials and readiness contract provide the starting point for implementing @@ -55,13 +55,13 @@ the test-key placeholder in this minimal setup: For live delivery, use the [shared settings table](../TECHNICAL.md#configure-environment-variables) for `EPP_PROVIDER_NAME`, both channel endpoints, authentication, and adapter-specific options. Keep `Values` as strings, including `EPP_PROVIDER_TIMEOUT_MS`, for example `"1500"`. -Replace placeholders; API keys belong in Key Vault, not this file. -A missing/invalid selected channel endpoint fails closed without fallback. +Replace placeholders; Store API keys in Key Vault. Each selected channel requires a valid endpoint; an invalid or +missing endpoint returns an error without using the other channel. Core Tools copies `Values` into the process environment; direct Node processes and offline tests do not load this file. [AppConfig](src/functions/config.js) reads `process.env` when `readConfig()` runs. -Restart after edits. Core Tools cannot resolve Key Vault references locally, so use only a local -test PEM or base64 PEM. Never copy Azurite settings into Azure. +Restart after edits. Core Tools cannot resolve Key Vault references locally, so use a local +test PEM or base64 PEM. Keep Azurite settings in the local environment. In Azure, configure the same app settings and use a Key Vault reference for the private PEM. Provider secrets use managed identity, not a developer CLI login. Keep local evaluation on loopback. @@ -69,8 +69,9 @@ Provider secrets use managed identity, not a developer CLI login. Keep local eva ## Request behavior `POST /api/SendOtp` follows the [shared request/response contract](../docs/CONTRACT.md#1-http-api). -Easy Auth authenticates; JWE decryption, nonce values, and request fields do not. -The handler never parses or echoes incoming `Authorization`. +Easy Auth authenticates the caller. JWE decryption, nonce values, and request fields serve other +parts of the contract. The handler leaves incoming `Authorization` processing to Easy Auth and +does not echo it. Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) for no-send validation: authentication and decryption still run, but request-path provider lookup, credentials, and HTTP do not. @@ -85,7 +86,8 @@ For message/voice mapping and credential rules, see the Use the provider-neutral [BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the Microsoft Entra ID payload, -caller authentication, and response rules. The customer owns the outbound service integration. +caller authentication, and response rules. Customers can connect the outbound phone service +that meets their requirements. For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and the [JavaScript service-event queries](../docs/MONITORING.md#4-javascript-service-event-queries). @@ -116,7 +118,7 @@ unset for local evaluation without credential acquisition; prewarming never send | [test/](test/) | Representative offline checks | To add a provider, implement its fixed `name`, `authenticationMode`, `credentialSpec`, -`createRequest` and `interpretResponse` in the provider folder, then add it to the switch in +`createRequest`, and `interpretResponse` in the provider folder. Then add it to the switch in [providers/index.js](src/functions/providers/index.js). `interpretResponse` returns a `ProviderResult` containing the provider-owned outcome, endpoint HTTP status and one safe fixed failure classification. Raw API JSON stays inside the provider. See diff --git a/python/README.md b/python/README.md index 4091dd5..70e0e42 100644 --- a/python/README.md +++ b/python/README.md @@ -20,15 +20,15 @@ the adapters in this folder. 1. Create the [local settings below](#environment-configuration). For live integration, choose a fixed provider ID from [providers/](src/providers/), configure its endpoints/options, and complete [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). - No provider is selected by default. + Select one provider explicitly. 2. Use a virtual environment, install [requirements.txt](requirements.txt) and pytest, and run [tests/](tests/). Start Core Tools here, **on loopback only, without tunnels**; it has no Easy Auth. 3. For manual deployment, publish this app to a compatible Linux Python Function with dependencies or a supported remote build. Inspect [.funcignore](.funcignore) and the package for private settings/keys. Complete the [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist) - before public access: it is the only caller-authentication gate; never disable it or exempt SendOtp. - Run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint); - offline tests do not verify platform authentication. + before public access. Keep Easy Auth enabled for SendOtp, then run + [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint) to verify + platform authentication. **Regional continuity:** use the [Front Door guide](../docs/FRONTDOOR.md) to implement and validate the readiness route and regional deployment for Python. The recorded trials used JavaScript, so @@ -54,13 +54,13 @@ For local evaluation, start Azurite and replace the test-key placeholder in this For live delivery, use the [shared settings table](../TECHNICAL.md#configure-environment-variables) for `EPP_PROVIDER_NAME`, both channel endpoints, authentication, and adapter-specific options. Keep values as strings, including `EPP_PROVIDER_TIMEOUT_MS: "1500"`. Replace placeholders; -API keys belong in provider-named Key Vault secrets, not this file. -A missing/invalid selected channel endpoint fails closed without fallback. +Store API keys in provider-named Key Vault secrets. Each selected channel requires a valid +endpoint; an invalid or missing endpoint returns an error without using the other channel. Core Tools loads `Values` into `os.environ`; direct Python execution and pytest do not. [read_config](src/config.py) returns an `AppConfig`, and the handler reads its attributes. -Restart after edits. Core Tools cannot resolve Key Vault references locally, so use only a local -test PEM or base64 PEM. Never copy Azurite settings into Azure. +Restart after edits. Core Tools cannot resolve Key Vault references locally, so use a local +test PEM or base64 PEM. Keep Azurite settings in the local environment. In Azure, configure the same app settings and use a Key Vault reference for the private PEM. Provider secrets use managed identity, not a developer CLI login. Keep local evaluation on loopback. @@ -68,8 +68,9 @@ Provider secrets use managed identity, not a developer CLI login. Keep local eva ## Request behavior `POST /api/SendOtp` follows the [shared request/response contract](../docs/CONTRACT.md#1-http-api). -Easy Auth authenticates; JWE decryption, nonce values, and request fields do not. -The handler never parses or echoes incoming `Authorization`. +Easy Auth authenticates the caller. JWE decryption, nonce values, and request fields serve other +parts of the contract. The handler leaves incoming `Authorization` processing to Easy Auth and +does not echo it. Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) for no-send validation: authentication and decryption still run, but request-path provider lookup, credentials, and HTTP do not. @@ -83,7 +84,8 @@ and [Telesign integration](../TECHNICAL.md#telesign-epp). Use the provider-neutral [BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the Microsoft Entra ID payload, -caller authentication, and response rules. The customer owns the outbound service integration. +caller authentication, and response rules. Customers can connect the outbound phone service +that meets their requirements. For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and [runtime-specific log discovery](../docs/MONITORING.md#runtime-specific-log-discovery). diff --git a/setup/docs/README.md b/setup/docs/README.md index 5382538..ef19361 100644 --- a/setup/docs/README.md +++ b/setup/docs/README.md @@ -132,8 +132,8 @@ After Step 2 approval, setup configures: - Microsoft Graph **beta** access for the Entra `signInAudienceRestrictions` allowed-tenants preview. The selected provider tenant is allowed in addition to the app's home tenant, which Entra always allows. See the [allowed-tenants schema](https://learn.microsoft.com/en-us/graph/api/resources/allowedtenantsaudience?view=graph-rest-beta). -- The selected **Linux FC1 or EP1** plan available in the region, with sufficient subscription - quota. Service availability and provider registration do not guarantee quota. Resolve +- The selected **Linux FC1 or EP1** plan available in the region, with enough subscription + quota. Check service availability, provider registration, and quota separately. Resolve [`SubscriptionIsOverQuotaForSku`](Troubleshooting.md#deployment-fails-with-subscriptionisoverquotaforsku) before retrying. After approval, setup registers required providers; the operator needs their subscription-scoped `/register/action` permission. @@ -145,12 +145,12 @@ After Step 2 approval, setup configures: ### Permissions are separate For an Azure built-in-role example, **Owner at the selected subscription** includes deployment, -resource-provider registration, and role-assignment rights. **Contributor alone is insufficient** -for `Microsoft.Authorization/roleAssignments/write`; Contributor plus **Role Based Access Control -Administrator** at the required scope is another administrator-reviewed option, subject to any -role-assignment conditions. Setup deploys at subscription scope and creates a new resource group; -a role on an unrelated existing group is not sufficient. Request only approved scope/duration -and activate eligible PIM roles before setup. See [Azure roles](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles). +resource-provider registration, and role-assignment rights. Another reviewed option is +**Contributor** plus **Role Based Access Control Administrator** at the required scope, subject +to any role-assignment conditions. Setup deploys at subscription scope and creates a new resource +group, so access to an unrelated group does not cover the deployment. Request the approved scope +and duration, and activate eligible PIM roles before setup. See +[Azure roles](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles). Azure roles do not grant Entra/Graph permissions, and Entra roles do not grant Azure/vault access. The Entra Privileged Role Administrator and directory permissions of the signed-in Azure CLI user @@ -327,9 +327,9 @@ at `Deploy this complete plan? Type Yes or No [No]`. Do not supply `-ApproveDepl that switch authorizes the deployment phase without asking again. This is a cancellation at the approval gate, not an offline simulation. The existing application -registration is still required. Before the plan appears, setup can download tools and packages, -request prerequisite installation, sign in through Azure CLI, perform permission and -region checks, verify package checksums, build the C# package locally when selected, and compile Bicep. +registration is still required. Before the plan appears, setup can download tools and packages, request required installation, +and sign in through Azure CLI. It also checks permissions and regions, verifies package checksums, +builds the C# package when selected, and compiles Bicep. Installation, sign-in, and consent prompts are separate from deployment approval. A preflight error is not a successful preview. @@ -366,10 +366,11 @@ The suffix does not include provider/region, so changing those inputs with the s subscription/app/prefix can reconfigure the existing endpoint. See [deployment separation and key constraints](../../docs/ONBOARDING.md#inputs-you-supply-to-setup). -The plan creates or updates a dedicated resource group, selected Linux FC1 or EP1 hosting plan, Function App, -storage account/private package container, Key Vault, Log Analytics workspace, Application Insights, -outbound managed identity, diagnostics, Easy Auth, and scoped role assignments. Storage/package -access uses managed identity, not account keys or Azure Storage shared access signatures. +The plan creates or updates the complete deployment. This includes a dedicated resource group, +Linux FC1 or EP1 hosting plan, Function App, storage, private package container, and Key Vault. +It also includes Log Analytics, Application Insights, an outbound managed identity, diagnostics, +Easy Auth, and scoped role assignments. Storage and package access use managed identity instead +of account keys or Azure Storage shared access signatures. Telemetry uses the system identity; the outbound identity is selected explicitly, not through a global `AZURE_CLIENT_ID`. @@ -584,11 +585,11 @@ if ($LASTEXITCODE -ne 0) { throw 'ServicePlans.Tests.ps1 failed.' } Remove-Item -LiteralPath $templatePath ``` -The suites cover commit-pinned launcher downloads and cleanup; cached account and subscription -selection; Azure CLI Graph transport; application and region selection; provider discovery, -allowlist preservation, and stale-plan rejection; certificate decisions and cleanup ordering; -provider/package resolution; and FC1/EP1 deployment paths. GitHub, Azure, Microsoft Graph, -certificate, and publication calls are mocked. CI runs the same eight suites on Windows and Linux. +The suites cover launcher downloads and cleanup; account, subscription, application, and region +selection; Azure CLI Graph calls; provider discovery; allowlist preservation; and stale-plan +checks. They also cover certificate choices, provider and package selection, and FC1/EP1 +deployment. GitHub, Azure, Microsoft Graph, certificate, and publication calls are mocked. +CI runs the same eight suites on Windows and Linux. They do not certify live deployments, RBAC propagation, Key Vault issuance, Entra behavior, or provider delivery. @@ -609,9 +610,9 @@ configuration APIs alone do not establish EPP feature availability or an EPP upd Do not invent a PATCH payload, assume a Graph success means activation, or use the general Delete operation as an EPP rollback. -Before a policy change, the **Authentication Policy Administrator** must obtain Microsoft's -current approved EPP procedure for the selected tenant/channel: tool, API/version and exact -field placement, required consent/roles, readback, validation, and rollback semantics. +Before a policy change, the **Authentication Policy Administrator** gets Microsoft's current +approved EPP procedure for the selected tenant and channel. It must identify the tool, API and +version, exact field placement, required consent and roles, readback steps, validation, and rollback. Delegated `Policy.ReadWrite.AuthenticationMethod` is a policy-management permission, not proof of EPP entitlement; confirm the approved procedure's permission requirements separately from setup. If this information or the feature is unavailable, leave policy unchanged and record the blocker. diff --git a/setup/docs/Troubleshooting.md b/setup/docs/Troubleshooting.md index 2de9fd7..25c663c 100644 --- a/setup/docs/Troubleshooting.md +++ b/setup/docs/Troubleshooting.md @@ -2,9 +2,10 @@ [Documentation](../../docs/README.md) | [Setup reference](README.md) | [Resume onboarding](../../docs/GETTING-STARTED.md) -Select the stage that failed. Keep existing resources in place, and never blindly retry a -live send. Before rerunning setup, read [safe recovery](#recover-without-destructive-cleanup). -After deployment, complete the provider and delivery checks separately. +Select the stage that failed and keep existing resources in place. Before rerunning setup, use +[safe recovery](#recover-without-destructive-cleanup). For a live-send timeout, check the provider +outcome before retrying. After deployment, complete provider acceptance and delivery checks +separately. ## Find your symptom @@ -26,7 +27,7 @@ subscription, resource group, runtime/release, UTC test time, and safe correlati Never attach complete settings, tokens, phone numbers, OTPs, private keys, encrypted bodies, or raw provider responses. Keep body tracing off. -| Symptom | First checks / owner | Do not do this | +| Symptom | First checks / owner | Safeguard | |---|---|---| | Cannot see an offer or EPP activation/test capability | Customer onboarding owner verifies the [Security Store and feature-access gate](../../docs/ONBOARDING.md#before-purchasing-or-deploying) with provider/Microsoft support. | Do not infer eligibility from Azure Owner or a successful purchase. | | Setup reports success, but no OTP delivery | Finish [Telesign vault entry or Soprano provider consent](../../docs/ONBOARDING.md#complete-provider-authentication); confirm authorized testing and policy activation are separately complete. | Do not redeploy all runtimes or paste credentials into local settings. | @@ -45,16 +46,17 @@ Evaluation success, provider acceptance, and recipient delivery are different ev ## Recover without destructive cleanup -1. **Locate the failure.** Before approval, checks do not mutate Azure resources, although sign-in +1. **Locate the failure.** Before approval, checks do not change Azure resources, although sign-in or Bicep installation may occur. After approval, partial Azure or Microsoft Entra ID changes can remain without a summary. Inspect the original error and **resource group > Deployments**. 2. **Fix, then rerun with the same reviewed inputs:** tenant, subscription, app, runtime, plan, - prefix, provider/route, and source/package versions. Changed IDs can create resources, break key - continuity, or reconfigure the endpoint. There is no checkpoint resume or automatic undo; use a - change window for active endpoints. -3. **Verify the failed stage and complete acceptance checks.** Never use resource-group deletion, - vault purge/recreation, encryption-credential removal, or shared-grant revocation as a retry. - Retirement uses the separate [rollback procedure](README.md#rollback-and-decommissioning). + prefix, provider/route, and source/package versions. This keeps resource names, keys, and + endpoint configuration consistent. Active endpoints need a change window because setup has + no checkpoint resume or automatic undo. +3. **Verify the failed stage and complete acceptance checks.** Keep resource-group deletion, + vault purge/recreation, encryption-credential removal, and shared-grant revocation out of + retry steps. Use the separate [rollback procedure](README.md#rollback-and-decommissioning) + when retiring a deployment. ## Certificate selection, reuse, or cleanup fails @@ -109,8 +111,8 @@ registration. A supported Linux EP1 region can still have zero quota for your su 5. Alternatively, choose another region only after checking its quota, service availability, and your residency and provider requirements. Review the updated deployment plan before approval. -**Expected:** sufficient effective quota, not merely a submitted request. Setup cannot guarantee -an increase. See [quota support](https://learn.microsoft.com/azure/quotas/quotas-overview). +**Expected:** the approved quota is visible and large enough for the deployment. A submitted +request alone is not enough. See [quota support](https://learn.microsoft.com/azure/quotas/quotas-overview). Do not change SKU, authentication, or provider credentials to bypass the error; rerun with reviewed inputs after quota is resolved, then validate the endpoint. From 48c013eb2d482b0a54487dfcb0f7bfb39a283887 Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 15 Oct 2026 15:07:45 -0700 Subject: [PATCH 13/13] docs: remove remaining documentation padding Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4fe53050-0d2b-4d05-bcc1-20f5b611b665 --- README.md | 95 +++++++---------- TECHNICAL.md | 141 +++++++++----------------- docs/APPLICATION-INSIGHTS.md | 49 +++------ docs/BRING-YOUR-OWN-PROVIDER.md | 60 ++++------- docs/CONTRACT.md | 33 ++---- docs/COST-CALCULATOR.md | 40 +++----- docs/FRONTDOOR.md | 76 +++++--------- docs/GETTING-STARTED.md | 100 +++++++----------- docs/LOGGING.md | 38 +++---- docs/MONITORING.md | 45 +++----- docs/MULTI-PROVIDER-IMPLEMENTATION.md | 31 ++---- docs/ONBOARDING.md | 90 ++++++---------- docs/README.md | 49 ++++----- docs/SIGN-IN-MONITORING.md | 65 ++++-------- dotnet/README.md | 43 ++++---- javascript/README.md | 54 ++++------ python/README.md | 57 +++++------ setup/docs/README.md | 121 ++++++++-------------- setup/docs/Troubleshooting.md | 104 ++++++++----------- 19 files changed, 469 insertions(+), 822 deletions(-) diff --git a/README.md b/README.md index d3ff5f4..b4735f4 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,9 @@ # External Phone Provider Azure Function Sample -Use this sample to connect Microsoft Entra ID to a phone provider for SMS and voice one-time -passwords. Guided setup deploys **one endpoint in one Azure region** and publishes the selected -JavaScript, C#, or Python package. +Connect Microsoft Entra ID to a phone provider for SMS and voice one-time passwords. +Guided setup deploys **one regional endpoint** using JavaScript, C#, or Python. -**Start in a separate nonproduction environment.** This keeps testing away from live resources -while supporting evaluation, implementation planning, and production preparation. Review -[requirements](#requirements) before deployment. +Start in a separate nonproduction environment and review [requirements](#requirements). ## Table of Contents @@ -23,91 +20,77 @@ while supporting evaluation, implementation planning, and production preparation ## Features -- One Azure Function endpoint that accepts Microsoft Entra ID SMS and voice requests and calls the configured provider. -- JavaScript, C#, and Python implementations of the same [HTTP and trust contract](docs/CONTRACT.md). -- Guided setup for Azure resources, package verification, publication, and Easy Auth configuration. -- Evaluation requests that authenticate and decrypt without sending a message to the provider. -- Monitoring guidance for Function requests, runtime events, Microsoft Entra ID sign-ins, and telemetry collection. -- A provider-neutral [bring-your-own-telephony integration path](docs/BRING-YOUR-OWN-PROVIDER.md). +- JavaScript, C#, and Python implementations of one [HTTP and trust contract](docs/CONTRACT.md). +- Guided Azure deployment with Easy Auth, Key Vault, and monitoring. +- No-send evaluation for authentication and decryption checks. +- Provider-neutral [phone-service integration](docs/BRING-YOUR-OWN-PROVIDER.md). ## Requirements -Before deploying: - -- Confirm [supported provider access and Microsoft's approved testing and activation procedure](docs/GETTING-STARTED.md#1-get-ready). - These define the supported path for setup, validation, and activation. -- Prepare a separate test environment with a nonproduction tenant and subscription, a new - application registration, and a unique resource prefix. -- Check the required [administrator roles, Azure permissions, tools, regional availability, and quota](setup/docs/README.md#prerequisites-for-step-2). -- Plan for Azure resource, telemetry, and provider charges. Use the [provider cost estimate](docs/COST-CALCULATOR.md) as planning input, not a billing report. -- Keep Easy Auth enabled from deployment through live operation so every request is checked - against the approved Microsoft caller. +- Confirm [provider access and Microsoft's approved test and activation process](docs/GETTING-STARTED.md#1-get-ready). +- Prepare a nonproduction tenant, subscription, app registration, and unique resource prefix. +- Check [roles, permissions, tools, regional availability, and quota](setup/docs/README.md#prerequisites-for-step-2). +- Plan for Azure, telemetry, and provider charges; the [cost estimate](docs/COST-CALCULATOR.md) is not a bill. +- Keep Easy Auth enabled for the approved Microsoft caller. ## Architecture ![Microsoft Entra ID sends an encrypted request through Easy Auth to a Function, which uses Key Vault and calls the phone provider](docs/images/single-region-architecture.png) -*Easy Auth checks the Microsoft caller, and the Function decrypts the request. -For live requests, success confirms provider acceptance; confirm delivery separately. Evaluation -sends nothing. Region, credentials, caching, and telemetry are illustrative: see -[architecture details](TECHNICAL.md#single-region-architecture). +*Easy Auth checks the Microsoft caller before the Function decrypts the request. +Live success confirms provider acceptance; confirm delivery separately. Evaluation sends nothing. +See [architecture details](TECHNICAL.md#single-region-architecture). [Open the full-size diagram](docs/images/single-region-architecture.png).* ## Get started Follow the **[five-step setup guide](docs/GETTING-STARTED.md)** in order: -1. [Get ready](docs/GETTING-STARTED.md#1-get-ready): confirm access, approvals, permissions, tools, and budget. -2. [Register your application](docs/GETTING-STARTED.md#2-register-your-application): save the tenant and application client IDs. -3. [Run guided setup](docs/GETTING-STARTED.md#3-run-guided-setup): choose a provider and runtime, then review the deployment plan before approving. -4. [Connect and test](docs/GETTING-STARTED.md#4-connect-your-provider-and-test): configure provider authentication, then complete approved evaluation and live checks for each intended channel. -5. [Activate and hand over](docs/GETTING-STARTED.md#5-activate-and-hand-over): have the policy administrator activate only after validation; assign monitoring, renewal, and rollback owners. +1. [Get ready](docs/GETTING-STARTED.md#1-get-ready): confirm access, approvals, permissions, and budget. +2. [Register the app](docs/GETTING-STARTED.md#2-register-your-application): save the tenant and client IDs. +3. [Deploy](docs/GETTING-STARTED.md#3-run-guided-setup): choose the provider, runtime, and plan. +4. [Connect and test](docs/GETTING-STARTED.md#4-connect-your-provider-and-test): configure provider access and run approved checks. +5. [Activate and hand over](docs/GETTING-STARTED.md#5-activate-and-hand-over): activate after validation and assign owners. -After validation, your policy administrator can activate the endpoint through the approved -procedure. Review the [production-readiness considerations](docs/CONTRACT.md#production-limitations) -as you plan the live service. +Before live use, review [production readiness](docs/CONTRACT.md#production-limitations). ## Guidance ### Deploy and configure -- [Five-step setup](docs/GETTING-STARTED.md) for a first deployment. -- [Customer configuration and validation](docs/ONBOARDING.md) for IDs, provider authentication, testing, and activation. -- [Setup reference](setup/docs/README.md) for permissions, hosting plans, certificate choices, unattended use, renewal, and rollback. -- [Troubleshooting](setup/docs/Troubleshooting.md) for symptom-led checks and safe recovery. +- [Five-step setup](docs/GETTING-STARTED.md) +- [Configuration and validation](docs/ONBOARDING.md) +- [Setup reference](setup/docs/README.md) +- [Troubleshooting](setup/docs/Troubleshooting.md) ### Operate - [Function monitoring and alerts](docs/MONITORING.md) -- [Application Insights collection diagnostics](docs/APPLICATION-INSIGHTS.md) -- [Microsoft Entra ID sign-in monitoring](docs/SIGN-IN-MONITORING.md) -- [Certificate renewal](setup/docs/README.md#encryption-certificate-lifecycle) and [rollback](setup/docs/README.md#rollback-and-decommissioning) -- [SMS and voice cost estimation](docs/COST-CALCULATOR.md) +- [Application Insights collection](docs/APPLICATION-INSIGHTS.md) +- [Microsoft Entra ID monitoring](docs/SIGN-IN-MONITORING.md) +- [Certificate renewal and rollback](setup/docs/README.md#encryption-certificate-lifecycle) +- [Provider cost estimate](docs/COST-CALCULATOR.md) ### Develop -- Runtime guides: [JavaScript](javascript/README.md), [C#](dotnet/README.md), and [Python](python/README.md) -- [Architecture, settings, and packages](TECHNICAL.md) -- [HTTP, status, configuration, and runtime contract](docs/CONTRACT.md) -- [Application log events and safe fields](docs/LOGGING.md) -- [Provider-neutral BYOT integration](docs/BRING-YOUR-OWN-PROVIDER.md) +- Runtime guides: [JavaScript](javascript/README.md), [C#](dotnet/README.md), [Python](python/README.md) +- [Architecture and settings](TECHNICAL.md) +- [HTTP contract](docs/CONTRACT.md) +- [Log schema](docs/LOGGING.md) +- [Phone-service integration](docs/BRING-YOUR-OWN-PROVIDER.md) ### Advanced topics -- [Multi-region routing with Azure Front Door](docs/FRONTDOOR.md) -- [Conservative primary-to-secondary provider fallback](docs/MULTI-PROVIDER-IMPLEMENTATION.md) - -Use these guides after validating the single-region endpoint. They cover regional routing, -provider fallback, capacity, validation, operations, and rollback. +- [Multi-region routing](docs/FRONTDOOR.md) +- [Provider fallback](docs/MULTI-PROVIDER-IMPLEMENTATION.md) ## Getting help -- For provider access, approved testing, or activation, use the - [provider and Microsoft support route](docs/ONBOARDING.md#before-purchasing-or-deploying). -- For setup or endpoint failures, start with [troubleshooting](setup/docs/Troubleshooting.md). -- For repository problems, use [GitHub issues](https://github.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/issues). +- [Provider access, testing, and activation](docs/ONBOARDING.md#before-purchasing-or-deploying) +- [Setup and endpoint troubleshooting](setup/docs/Troubleshooting.md) +- [Repository issues](https://github.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/issues) Keep credentials, phone data, OTPs, encrypted bodies, and raw provider responses out of issues and support requests. See the [complete documentation index](docs/README.md). diff --git a/TECHNICAL.md b/TECHNICAL.md index b7e74eb..9cc60b3 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -15,32 +15,21 @@ or [security](#security). For regional continuity, see See the [single-region diagram in the sample overview](README.md#architecture). -Microsoft Entra ID sends an authenticated, encrypted request. Easy Auth checks the caller, -then the Function decrypts the request using its Key Vault-backed key. For a live request, the -selected adapter submits the message and returns success after provider acceptance, not after -confirmed recipient delivery. Application Insights collects the available request and log telemetry. - -The diagram is an overview, not a portal screenshot. East US is illustrative; select a region -with capacity and quota for your hosting plan. The provider-secret path applies to API-key -integrations; OAuth integrations use managed-identity token exchange instead. Credential caching -depends on the [selected cache settings](docs/CONTRACT.md#credential-caching-and-refresh), and -[log formats vary by runtime](docs/MONITORING.md#runtime-specific-log-discovery). - -An authorized, valid encrypted **evaluation request (`mode: 2`)** returns the matching nonce -without submitting a message to the provider. When caching is enabled, credential preparation -can run independently; it never sends an OTP. Authentication failures can return **401 or 403**, -neither of which is a successful evaluation. +Microsoft Entra ID sends an authenticated, encrypted request. Easy Auth checks the caller, the +Function decrypts the request, and the selected adapter submits a live message. Success means +provider acceptance, not confirmed delivery. -## Resiliency and business continuity +East US is illustrative. API-key integrations use provider secrets; OAuth integrations use +managed-identity token exchange. See [credential caching](docs/CONTRACT.md#credential-caching-and-refresh) +and [runtime logs](docs/MONITORING.md#runtime-specific-log-discovery). + +An authorized **evaluation request (`mode: 2`)** returns the nonce without sending a message. +Authentication failures return **401 or 403**. -Start with a validated single-region deployment. For regional continuity, the -[Azure Front Door design](docs/FRONTDOOR.md) uses one public URL across separate regional -origins while preserving the SendOtp contract. +## Resiliency and business continuity -The guide covers capacity, shared encryption keys, regional dependencies, safe failover drills, -and rollback. It also defines a non-delivering readiness endpoint for each regional package. -Add regional deployment and readiness to the approved BCDR process, then activate after both -failover directions pass the documented checks. +The [Azure Front Door design](docs/FRONTDOOR.md) adds regional origins behind one public URL. +It covers capacity, shared keys, dependencies, readiness, failover drills, and rollback. ## Implementations @@ -50,37 +39,26 @@ failover directions pass the documented checks. | C# (.NET isolated worker) | Available | [dotnet/](dotnet/) | | Python (v2 model) | Available | [python/](python/) | -Choose **one language and one provider per Function App**; no provider is preferred or selected -by default. All implementations share the [HTTP, configuration, and trust contract](docs/CONTRACT.md), -but provider APIs, binding errors, credential caching, and telemetry can differ by runtime. +Choose **one language and one provider per Function App**. All implementations share the +[HTTP, configuration, and trust contract](docs/CONTRACT.md); runtime behavior can differ. -For [local development or manual deployment](docs/ONBOARDING.md#optional-local-development-and-manual-deployment), -follow the selected runtime guide. For a custom service integration, start with the -[provider-neutral BYOT interface](docs/BRING-YOUR-OWN-PROVIDER.md), then implement and validate -the outbound adapter through the selected runtime. +For local work, use the selected runtime guide. For a custom integration, start with +[Bring your own provider](docs/BRING-YOUR-OWN-PROVIDER.md). ## Guided EPP setup -Follow the [five-step guide](docs/GETTING-STARTED.md), including its access and approval gates. -Download only `Setup-Epp.ps1`; it retrieves its support files from one commit and deploys after -you approve the complete plan. The [setup reference](setup/docs/README.md#step-2---download-and-run-one-script) -explains provider, route, runtime, hosting, and resource choices. +Follow the [five-step guide](docs/GETTING-STARTED.md). `Setup-Epp.ps1` retrieves its support files +from one commit and deploys after plan approval. See the [setup reference](setup/docs/README.md#step-2---download-and-run-one-script). -Review [application permissions and caller trust](setup/docs/README.md#step-1---manually-create-the-application), -including tenant-wide Graph `Application.Read.All`, and the -[certificate selection rules](setup/docs/README.md#encryption-certificate-selection). -Setup pins one PEM secret version and verifies Function access before changing registered encryption -credentials. Only the public certificate is downloaded. Replacement requires coordinated maintenance, -not zero-downtime rotation; certificate synchronization and policy activation remain manual. +Review [application permissions](setup/docs/README.md#step-1---manually-create-the-application) +and [certificate selection](setup/docs/README.md#encryption-certificate-selection). Setup pins one +PEM secret version and downloads only the public certificate. One deployment accepts SMS and voice; each request's `channel` selects its configured provider URL. ## Download a Function ZIP -**Optional developer/manual deployment path:** guided setup already selects, verifies, builds as -needed, and publishes the package. Customers using it should continue with -[provider authentication and validation](docs/ONBOARDING.md#complete-provider-authentication); -the deployed package is ready for that handoff. +Guided setup already publishes the package. Use this section for manual deployment. Download the latest successful CI ZIP for your chosen language: @@ -90,22 +68,15 @@ Download the latest successful CI ZIP for your chosen language: | .NET | [epp-dotnet-source.zip](https://github.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/releases/latest/download/epp-dotnet-source.zip) | C# Function source and project file; build/publish before deployment | | Python | [epp-python-source.zip](https://github.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/releases/latest/download/epp-python-source.zip) | Source for Azure remote build on Linux | -Downloads need no PowerShell or build tools. Verify the release's `SHA256SUMS.txt`, then configure -the target runtime, app settings, Key Vault access, and **Easy Auth before deployment**. -Build/publish .NET source; use Azure remote build for Python dependencies. Neither source ZIP is -directly runnable as a run-from-package artifact. **Code > Download ZIP** is the repository, not -a deployment package. +Verify `SHA256SUMS.txt`, configure the runtime, settings, Key Vault, and **Easy Auth**, then build +.NET or use Python remote build. Source ZIPs are not run-from-package artifacts. -Each successful `main` build tests all three runtimes and inspects the ZIPs before publishing a -versioned [latest release](https://github.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/releases/latest). -These links and guided setup track that release; older assets remain available and are not overwritten. -PR builds publish workflow artifacts only, which may require GitHub sign-in. Public releases do not. -Packaging neither deploys nor verifies provider delivery. +Successful `main` builds publish a versioned [release](https://github.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/releases/latest). +PR builds publish workflow artifacts only. Packaging does not deploy or verify delivery. ## Build ZIPs Locally -For custom builds, run the script for your chosen language from the repository root. These standalone scripts create -ZIPs locally; they do not sign in to Azure, upload code, or change app settings. +Run one packaging script from the repository root. It creates a local ZIP without changing Azure. | Language | Root-level script | Prerequisites | ZIP in `artifacts/` | |---|---|---|---| @@ -119,28 +90,19 @@ pwsh -File ./package-dotnet.ps1 pwsh -File ./package-python.ps1 ``` -Choose one command; each packages only its language. The scripts locate source relative to their -own location, so invoking an absolute script path also works from another directory. Each ZIP has -`host.json` at its root, with no enclosing language folder. Local settings, credential files, and -first-party tests are excluded. Generated ZIPs are ignored by Git. - -JavaScript installs production dependencies from the lockfile in a temporary folder; your working -`node_modules` is not copied or modified. Dependency lifecycle scripts are disabled for this sample's -JavaScript dependencies. When adding native dependencies or packages that require install scripts, -review packaging and build them for the target Azure OS. - -**The .NET download contains source code.** It includes `dotnet.csproj`, `host.json`, `Program.cs`, -and the C# files under `Functions/` and `Src/`. Packaging does not run restore, build, or publish, -and needs no .NET SDK. It excludes `bin/`, `obj/`, tests, local settings, and compiled dependencies. -Customers extract it and run `dotnet publish dotnet.csproj --configuration Release --output ../publish` -with the .NET 8 SDK, or use a deployment pipeline that builds the project. Deploy the resulting -publish output with `host.json` at its root, not the source ZIP directly. CI tests this customer -build from an extracted copy; that temporary publish output is not included in the download. - -**The Python download contains source code.** Deploy it to a Linux Function App with remote build -enabled so Azure installs `requirements.txt`. Use the supported remote-build path instead of -run-from-package or Windows-installed dependencies. The packaging script does not run pip or -include a local virtual environment. +Choose one command. Each ZIP has `host.json` at its root and excludes local settings, credentials, +and tests. + +JavaScript installs production dependencies from the lockfile in a temporary folder. Review +packaging for native dependencies or required lifecycle scripts. It does not modify `node_modules`. + +**The .NET download contains source.** Run +`dotnet publish dotnet.csproj --configuration Release --output ../publish` with .NET 8, then deploy +the publish output with `host.json` at its root. The archive includes `dotnet.csproj`, +`Program.cs`, `Functions/`, and `Src/`, and excludes `bin/` and `obj/`. + +**The Python download contains source.** Deploy it to Linux with remote build so Azure installs +`requirements.txt`. Existing archives are never overwritten. For another build, specify a new path: @@ -148,11 +110,8 @@ Existing archives are never overwritten. For another build, specify a new path: pwsh -File ./package-javascript.ps1 -OutputPath ./artifacts/epp-javascript-v2.zip ``` -The same `-OutputPath` option works for all three scripts. Configure the destination app's runtime, -app settings, Key Vault access, and Easy Auth separately before deployment. See -[deployment and validation](docs/ONBOARDING.md#4-package-deploy-and-verify). Packaging success does -not verify cloud configuration or provider delivery. File selection is tailored to this sample; -extend it deliberately when adding runtime assets, and never put secrets in application source. +`-OutputPath` works for all three scripts. Configure the destination and follow +[deployment and validation](docs/ONBOARDING.md#4-package-deploy-and-verify). Never include secrets. ## The design in one line @@ -168,19 +127,13 @@ not in application environment settings or incoming request data. ## Configure environment variables -For guided deployment, use the [customer values and ownership table](docs/ONBOARDING.md#values-and-ownership) -to inspect what setup already configured. The following sample/local instructions are for -developers and manual deployments; they are not an extra guided-setup step. +For guided deployment, inspect the [values and ownership table](docs/ONBOARDING.md#values-and-ownership). +The following settings are for local or manual deployment. -Use the [sample settings](docs/local.settings.sample.json) as the starting point for the chosen -runtime. All entries in its `Values` object are **strings**. The application reads environment -variables; Azure Functions Core Tools loads that `Values` object for local runs. +Start from [sample settings](docs/local.settings.sample.json). Every `Values` entry is a string. -The sample uses `node`; change it to `python` or `dotnet-isolated` for those runtimes. Replace the -provider, endpoint, vault and test-key placeholders before use. Its storage value assumes **Azurite -is running**; do not copy `UseDevelopmentStorage=true` into Azure. Optional settings stay in the table -below rather than appearing as required placeholders in the sample. Keep explanatory comments outside -`Values`, otherwise the host loads them as environment variables too. +Change `node` to `python` or `dotnet-isolated` as needed. Replace placeholders. The sample storage +value requires **Azurite** and must not be copied to Azure. Local settings supply environment variables, **not a serialized `AppConfig` or request model**. For example, `EPP_PROVIDER_NAME` becomes `config.providerName` in JavaScript, diff --git a/docs/APPLICATION-INSIGHTS.md b/docs/APPLICATION-INSIGHTS.md index 2804f9b..8ed5f79 100644 --- a/docs/APPLICATION-INSIGHTS.md +++ b/docs/APPLICATION-INSIGHTS.md @@ -4,17 +4,11 @@ [Documentation](README.md) | [Monitoring walkthrough](MONITORING.md) | [Log schema](LOGGING.md) -For logs and alerts, use the [monitoring walkthrough](MONITORING.md). -For missing or incomplete telemetry, use -[single-region configuration](#single-region-configuration). This page covers JavaScript, -.NET isolated, and Python. +Use the [monitoring walkthrough](MONITORING.md) for queries and alerts. Use +[single-region configuration](#single-region-configuration) for missing telemetry. -Function telemetry shows execution, timing, and logs. Confirm recipient delivery through the -provider's supported delivery evidence. [Microsoft Entra ID sign-in monitoring](SIGN-IN-MONITORING.md) -uses a separate tenant export pipeline for identity activity. - -This page assumes the endpoint is already deployed. Use the [setup guide](GETTING-STARTED.md) -first, then follow the [approved test handoff](ONBOARDING.md#validate-the-deployed-endpoint). +Function telemetry covers execution, timing, and logs. Provider delivery evidence and +[Microsoft Entra ID sign-in monitoring](SIGN-IN-MONITORING.md) are separate. Sections: [collection pipelines](#how-telemetry-reaches-application-insights), [signals and tables](#signals-tables-and-metrics-are-different-things), @@ -24,27 +18,20 @@ or [sampling/privacy limits](#sampling-privacy-and-collection-limits). ## How telemetry reaches Application Insights -The **Functions host** dispatches invocations and records their telemetry; the **language worker** -runs endpoint code. Standard worker logs pass through the host. A direct worker SDK/OpenTelemetry -exporter instead needs its own initialization, authentication, filtering, and sampling. -Host configuration alone does not configure every worker signal. +The **Functions host** records invocation telemetry; the **language worker** runs endpoint code. +A direct worker exporter needs separate initialization, authentication, filtering, and sampling. -Exporters send data to Application Insights; its linked Log Analytics workspace stores records -for views and queries. Propagated correlation context connects requests, dependencies, and logs. -Background work need not belong to a request. +Application Insights sends records to its linked Log Analytics workspace. Correlation context +connects requests, dependencies, and logs. -In OpenTelemetry terminology, a distributed trace contains spans. In Application Insights, -server spans generally appear as **requests**, client spans as **dependencies**, and -application log messages as **traces**. The `traces` table is not a list of all distributed -tracing spans. +Application Insights generally stores server spans as **requests**, client spans as +**dependencies**, and log messages as **traces**. ### What the checked-in sample configures -The [deployment template](../setup/infra/resources.bicep) defines a workspace-based Application -Insights resource, the Function's connection and authentication settings, and a role assignment -for ingestion. It also defines a **separate** Function diagnostic setting that sends `allLogs` -and `AllMetrics` to the workspace. Diagnostic settings do not install worker instrumentation -and do not mean every Azure resource or every dependency is being observed. +The [deployment template](../setup/infra/resources.bicep) configures Application Insights, +authenticated ingestion, and a separate `allLogs`/`AllMetrics` diagnostic setting. Diagnostic +settings do not instrument every worker or dependency. | Implementation | Checked-in behavior | What not to assume | | --- | --- | --- | @@ -52,19 +39,15 @@ and do not mean every Azure resource or every dependency is being observed. | [JavaScript](../javascript/host.json) | Uses standard host Application Insights settings with sampling enabled and `Request` excluded from sampling. | The [package manifest](../javascript/package.json) does not include a direct Application Insights/OpenTelemetry exporter. Host integration is not proof that all outbound Node.js HTTP calls are traced. | | [Python](../python/host.json) | Uses standard host Application Insights settings with sampling enabled and `Request` excluded from sampling. | The [requirements](../python/requirements.txt) do not include an Azure Monitor/OpenTelemetry exporter. A Python log message or `requests` call is not automatically a structured custom event or a dependency span. | -These are source defaults, not an inventory of a running deployment. Deployed package versions, -application-setting overrides, runtime versions, and log filters can change the result. The -guided setup can publish a released package rather than the current source. +These are source defaults. Packages, settings, runtime versions, and filters can change them. [Application events](LOGGING.md#event-sequence) are **logs**, not guaranteed `customEvents`. Use [runtime-specific discovery](MONITORING.md#runtime-specific-log-discovery) before sharing queries: exporters can change formatting, casing, and which properties survive. -To add direct worker instrumentation, follow the +For direct worker instrumentation, follow the [Functions OpenTelemetry guidance](https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto) -for the selected language. Initialize the exporter, instrumentation, and credentials, then verify -correlation and duplicate suppression. Use the settings for that runtime. For languages outside -this sample, check the matching Microsoft support tab. +for the selected language and verify correlation and duplicate suppression. ## Telemetry signals and tables diff --git a/docs/BRING-YOUR-OWN-PROVIDER.md b/docs/BRING-YOUR-OWN-PROVIDER.md index 809c595..67051f5 100644 --- a/docs/BRING-YOUR-OWN-PROVIDER.md +++ b/docs/BRING-YOUR-OWN-PROVIDER.md @@ -4,14 +4,11 @@ [Documentation](README.md) | [Exact HTTP contract](CONTRACT.md) | [Runtime guides](README.md#develop) -This guide shows how to connect a phone service to the **External Phone Provider (EPP)** -contract. **Microsoft Entra ID** sends the endpoint an encrypted phone number and one-time -passcode message. The endpoint verifies the caller, decrypts the details, and either evaluates -the request without sending or uses the connected phone service. +Connect a phone service to the **External Phone Provider (EPP)** contract. **Microsoft Entra ID** +sends an encrypted delivery request; the endpoint verifies, decrypts, evaluates, or sends it. -Before deploying, ask your tenant administrator to confirm -[eligibility, testing, and activation with Microsoft](ONBOARDING.md#before-purchasing-or-deploying). -Deploying the endpoint alone does not enable tenant policy. +Confirm [eligibility, testing, and activation](ONBOARDING.md#before-purchasing-or-deploying) +before deployment. Follow [caller verification](#1-verify-the-caller), [decryption](#2-read-and-decrypt-the-request), [evaluation/live handling](#3-evaluate-or-send), and [responses](#4-respond-to-entra-id). @@ -21,22 +18,15 @@ Then [register your integration](#where-to-add-your-integration) and [validate b ## 1. Verify the caller -Entra ID sends **`POST /api/SendOtp` over HTTPS**, with a signed bearer token in `Authorization`. -In this sample, **Easy Auth** (Azure App Service Authentication) checks the token and authorizes -the caller before the Function runs. Keep Easy Auth enabled because the anonymous Function -trigger relies on this platform check. +Entra ID sends **`POST /api/SendOtp` over HTTPS** with a signed bearer token. **Easy Auth** +authorizes the caller before the anonymous Function trigger runs. -Require HTTPS and authentication, return `401` for unauthenticated requests, and restrict access -to the trusted tenant issuer, endpoint-app audience, and authorized Microsoft caller application ID. -Your Azure operator should use the endpoint app registration and -[approved setup configuration](../setup/support/Epp.Setup.psm1) for these values, then verify -**Function App > Authentication**. Follow the -[trust checklist](ONBOARDING.md#manual-deployment-trust-checklist) for exact settings, -caller-ID guidance, and token-version details. +Require HTTPS, return `401` for unauthenticated requests, and restrict access to the trusted +issuer, audience, and Microsoft caller. Follow the +[trust checklist](ONBOARDING.md#manual-deployment-trust-checklist). -**Encryption is not caller authentication.** Anyone with the public key can create a JWE -(JSON Web Encryption) payload. Neither its nonce, the body's `tenantId`, nor tracing or -forwarded principal headers prove that Entra ID sent it. +**Encryption is not caller authentication.** A JWE, nonce, `tenantId`, or tracing header does +not prove that Entra ID sent the request. ## 2. Read and decrypt the request @@ -52,8 +42,8 @@ The JSON body is an envelope with these fields. Use the forms below across all s | `correlationId` | No | Nonempty string for sign-in tracing. | | `ttlSeconds` | No | Positive JSON integer up to `2147483647`, or omit it. Use `60`, not `"60"`, `60.0`, `6e1`, or null. | -Channel and mode names are case-insensitive. Reject unsupported versions, channels, or modes. -TTL is only checked as a number; it does not enforce passcode expiry or a delivery deadline. +Channel and mode names are case-insensitive. TTL validation does not enforce passcode expiry +or a delivery deadline. @@ -62,15 +52,11 @@ for content encryption. Accept exactly five nonempty compact-JWE segments, at mo characters**. Pass the original JWE, including its protected-header bytes, to a JOSE library. Verify the authentication tag before using the decrypted UTF-8 JSON. -The sample decrypts in-process with one RSA private key in `EPP_DECRYPTION_KEY_PEM`: a PEM key -or certificate bundle containing it, as text or base64-encoded PEM. In Azure, use a -managed-identity-resolved Key Vault secret reference. The matching public encryption credential -belongs to the endpoint app registration. +The sample uses one RSA private key in `EPP_DECRYPTION_KEY_PEM` as text or base64 PEM. In Azure, +use a managed-identity-resolved Key Vault reference. -The header's `kid` does not select a key in this sample. `EPP_ENCRYPTION_KEY_ID` only enables -a warning on mismatch after successful decryption. Setup pins one certificate's secret version; -renewal needs coordination because Key Vault renewal alone does not update the app registration -or pinned reference. See the [certificate lifecycle](../setup/docs/README.md#encryption-certificate-lifecycle). +The JWE `kid` does not select a key. `EPP_ENCRYPTION_KEY_ID` only warns after decryption. +See the [certificate lifecycle](../setup/docs/README.md#encryption-certificate-lifecycle). The decrypted delivery context contains: @@ -91,15 +77,11 @@ For parser and wrong-type handling, see [runtime binding differences](CONTRACT.m ## 3. Evaluate or send -**Evaluation (`mode: 2` or `"evaluation"`):** authenticate, validate, and decrypt, then return -the success response below **without sending anything**. This request path skips service -selection, credential lookup, and outbound HTTP; no service configuration is needed. -Configured workers may independently prepare or refresh credentials, but that does not send a message. +**Evaluation (`mode: 2` or `"evaluation"`):** authenticate, validate, decrypt, and return success +without service selection, credential lookup, outbound HTTP, or delivery. -**Live (`mode: 1` or `"live"`):** the customer implements and maintains the service integration. -Map the requested SMS or voice delivery to your service, preserving the destination, message, -and passcode. Handle voice rendering and locales as needed. Authenticate with your own service -credentials; **never forward the incoming bearer token**. +**Live (`mode: 1` or `"live"`):** map SMS or voice to your service while preserving the +destination, message, and passcode. **Never forward the incoming bearer token**. Return success after the integration recognizes provider acceptance. A queued or pending result counts only when the integration defines it as accepted. Map unknown, incomplete, or failed HTTP diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md index b210fce..4e0979d 100644 --- a/docs/CONTRACT.md +++ b/docs/CONTRACT.md @@ -4,38 +4,28 @@ [Documentation](README.md) | [Implementation walkthrough](BRING-YOUR-OWN-PROVIDER.md) | [Settings and packages](../TECHNICAL.md) -This page defines the shared contract for [JavaScript](../javascript/), [Python](../python/) and -[.NET](../dotnet/). Use the [production-readiness considerations](#production-limitations) when -planning a live service. +Shared contract for [JavaScript](../javascript/), [Python](../python/), and [.NET](../dotnet/). Go to [request/response](#1-http-api), [HTTP outcomes](#2-outcome--http-status-mapping), [provider contract](#3-provider-contract), [settings](#4-configuration-app-settings--env), [caching](#credential-caching-and-refresh), [required behaviors](#5-required-behaviors), [logs](#application-logs), or [tests](#6-lightweight-tests). -> **Naming.** EPP means **External Phone Provider**. App settings use the `EPP_` prefix; the -> request and delivery models are `Envelope`, `DeliveryContext` and provider-neutral OTP delivery. -> Documentation names do not change the external JSON fields or `microsoft.mfa.otpDeliver.v1` version. +> **Naming:** EPP means **External Phone Provider**. App settings keep the `EPP_` prefix; +> documentation names do not change external fields or `microsoft.mfa.otpDeliver.v1`. -Each deployment selects one provider. The HTTP Function owns the request lifecycle, while -API-specific credentials, paths, headers, payloads, response rules, outcomes and safe failure -classifications remain inside each provider implementation. +Each deployment selects one provider. Provider-specific API behavior stays inside its implementation. -For regional continuity, the [Front Door design](FRONTDOOR.md) routes this same API to regional -origins while preserving caller authentication, JWE validation, nonce semantics, and provider -selection. Its readiness endpoint is separate from this contract and never delivers OTPs. -Provider fallback and delivery deduplication remain separate designs. +The [Front Door design](FRONTDOOR.md) routes this API to regional origins. Its readiness endpoint +is separate and never delivers OTPs. --- ## 1. HTTP API -**Endpoint:** `POST /api/SendOtp` (Functions HTTP trigger, `authLevel: anonymous`). **Easy Auth is the -only caller-authentication boundary and runs before the handler**; the application has no token -validator or function-key gate. This is the interface **Microsoft Entra ID** calls. -Entra ID sends a signed bearer JWT; Easy Auth validates it, not the handler. -PII (phone number + the rendered message, which contains the passcode) is **encrypted** inside a JWE; -the cleartext envelope carries routing/scheduling only. +**Endpoint:** `POST /api/SendOtp` (`authLevel: anonymous`). **Easy Auth is the caller-authentication +boundary before the handler**. The phone number and message are encrypted inside a JWE; the +cleartext envelope carries routing data. ### Request headers @@ -46,9 +36,8 @@ the cleartext envelope carries routing/scheduling only. | `x-ms-correlation-id` | tracing only; fallback for envelope `correlationId`, not authentication | | `x-ms-client-request-id` | per-attempt tracing id (used as `messageId`), not authentication | -Easy Auth provides caller trust. Forwarded headers such as `x-ms-client-principal` provide context, -not authentication. The handler does not use them to authenticate callers or forward the incoming -`Authorization` header to the provider. Configure Easy Auth as described in section 5. +Forwarded headers such as `x-ms-client-principal` provide context, not authentication. The handler +never forwards `Authorization` to the provider. ### EPP request body diff --git a/docs/COST-CALCULATOR.md b/docs/COST-CALCULATOR.md index 0938387..a1e38f7 100644 --- a/docs/COST-CALCULATOR.md +++ b/docs/COST-CALCULATOR.md @@ -1,24 +1,17 @@ # Estimate SMS provider costs -Use this guide to create a planning estimate for SMS traffic before onboarding an -External Phone Provider (EPP). The report finds SMS and voice authentication steps in -recent sign-in activity and groups them by the phone number's international calling -code, such as `+1` or `+91`. Apply your provider's destination rates to the report to -estimate a budget. - -**This is an estimate, not a usage or billing report.** A sign-in does not prove that -the provider accepted or delivered an SMS or voice call. Authentication details can -contain multiple SMS or voice steps for one sign-in, so this guide counts each matching -step as one estimated request. Actual provider billing can differ because of retries, -fallback, failures, message segmentation, and provider-specific charging rules. Confirm -the assumptions, supported destinations, taxes, fees, and final pricing with your provider. +Estimate SMS and voice traffic before onboarding an External Phone Provider (EPP). The report +groups recent authentication steps by calling code, such as `+1` or `+91`. Apply provider rates +to estimate a budget. + +**This is not a billing report.** It counts each matching authentication step as one estimated +request. Confirm retries, fallback, segmentation, destinations, taxes, fees, and pricing with +the provider. The `authenticationDetails` data used by this guide is currently available through the [Microsoft Graph beta API](https://learn.microsoft.com/en-us/graph/api/resources/authenticationdetail?view=graph-rest-beta). -Beta APIs are subject to change and are not supported for production applications. Review -and test this reporting script before each planning exercise. Because sign-in details mask -phone numbers, the script matches their visible trailing digits to the user's registered -phone authentication methods and derives the calling code from the matched registration. +Review the beta API and script before each planning exercise. The script matches masked trailing +digits to registered phone methods to derive the calling code. ## Prerequisites @@ -28,17 +21,12 @@ account must be allowed to consent to or use these Microsoft Graph delegated per - `AuditLog.Read.All` - `UserAuthenticationMethod.Read.All` -Reading other users' phone methods also requires a +Reading other users' phone methods requires a [supported Entra role](https://learn.microsoft.com/en-us/graph/api/authentication-list-phonemethods?view=graph-rest-1.0#permissions), -not just consent to those scopes. Authentication Administrator receives masked phone numbers; -if the calling code is unavailable, the script reports `Unknown`. Confirm approved read access -with your identity administrator rather than granting broader roles just to fill the report. - -Access to sign-in logs also depends on your Microsoft Entra licensing, directory role, -and log-retention period. Review the requested permissions before consenting. The script -processes sign-in authentication details that can include phone numbers. It exports only -aggregated calling codes, methods, and counts, not full phone numbers. Store, share, and -delete the output according to your organization's privacy and retention requirements. +Authentication Administrator receives masked phone numbers; unresolved calling codes appear as `Unknown`. + +Access also depends on Microsoft Entra licensing, roles, and retention. The script exports +aggregated calling codes, methods, and counts, not full phone numbers. Protect the output. Create `C:\temp` before running the script, or change the `Export-Csv` path to an approved folder. diff --git a/docs/FRONTDOOR.md b/docs/FRONTDOOR.md index d8fd924..c9cd1aa 100644 --- a/docs/FRONTDOOR.md +++ b/docs/FRONTDOOR.md @@ -5,58 +5,43 @@ [Documentation](README.md) | [Single-region setup](GETTING-STARTED.md) | [Routing troubleshooting](../setup/docs/Troubleshooting.md#optional-front-door-deployments) -Azure Front Door provides **one public EPP URL backed by Function Apps in multiple regions**. -Start with a validated [single-region deployment](GETTING-STARTED.md), then use this guide to -plan and implement the regional origins, readiness route, Front Door configuration, validation, -failover drills, and rollback. Include transition errors in the acceptance checks. +Azure Front Door provides **one public EPP URL across regional Function Apps**. Start with a +validated [single-region deployment](GETTING-STARTED.md), then add origins, readiness, routing, +validation, failover drills, and rollback. Follow [scope](#scope-and-observed-behavior), [architecture](#architecture), [plan/capacity](#1-plan-the-regional-deployment), [origins/keys](#2-prepare-equivalent-independently-provisioned-origins), [readiness](#3-provide-a-non-delivering-readiness-endpoint), [routing](#4-configure-front-door-manually), [validation](#5-validate-before-manually-activating-policy), and [fire-drill](#6-regional-failover-fire-drill). -Use [historical results](#observed-failover-results-and-limitations) as background for the -acceptance plan. After validation, follow [operations and rollback](#operations-and-rollback). +Use [historical results](#observed-failover-results-and-limitations) as test context. ## Scope and observed behavior -Testing used isolated JavaScript origins, Front Door Standard, two/three regions, a shared key, -and a separate non-delivering readiness handler. It covered encrypted evaluation, invalid callers, -direct-origin restrictions, and individual Function stop/restart. +Testing used JavaScript origins, Front Door Standard, two or three regions, a shared key, and a +non-delivering readiness handler. -Implement the [readiness contract](#3-provide-a-non-delivering-readiness-endpoint) as part of each -regional package and validate it before enabling probes. The released packages remain the -single-region baseline; include runtime-specific validation for .NET or Python. +Add the [readiness contract](#3-provide-a-non-delivering-readiness-endpoint) to each regional +package before enabling probes. Released packages remain single-region. -The recorded trials cover encrypted evaluation, caller and origin restrictions, and controlled -Function stop/restart. Build the acceptance plan around the features in use. It can include approved live SMS and voice -checks, real Microsoft Entra caller behavior, regional dependency loss, custom domains, WAF or -Private Link, and realistic load. +Build acceptance tests around the selected runtime and features, including approved live checks, +Microsoft caller behavior, dependency loss, networking, and realistic load. ### Regional resiliency responsibility -Microsoft defines, configures, documents, and validates its standard regional-failover baseline -and exercise procedure. Customers operate that baseline: ownership, capacity, credentials and -keys, monitoring, and approved drills. An alternate topology becomes a customer-owned design -with its own validation plan. +Customers own capacity, credentials, keys, monitoring, and approved drills. Validate any +alternate topology separately. -Use single-region onboarding as the foundation, then add Front Door, readiness, dependency -isolation, survivor capacity, real-caller checks, and regional-outage evidence. Deployment owners -set RTO/RPO, tolerable failures, latency, and evidence requirements; historical results provide -test context rather than those targets. +Set RTO/RPO, failure, latency, capacity, and evidence targets for the regional design. ## Architecture ![Multi-region External Phone Provider architecture with Azure Front Door](images/multi-region-architecture.png) -The diagram shows the design, not the exact test deployment. Its region names are examples. -Custom domains and Premium/WAF are optional and were not part of the Standard-tier test. -Give each Function App, storage account, and Key Vault a globally unique name. Each region also has -its own identity IDs and vault references. Keep the **provider and authentication configuration -consistent and use the same RSA key**, rather than copying every setting literally. -The SMS/voice arrows show live delivery. Evaluation returns the nonce without contacting the provider. -The diagram's provider-secret arrows apply to API-key integrations; OAuth uses each region's -outbound managed identity. Match the key material across regions, not the vault URLs or version IDs. +Region names are examples. Give each Function App, storage account, and Key Vault a unique name. +Keep **provider and authentication configuration consistent and use the same RSA key**. +Evaluation returns the nonce without contacting the provider. API-key integrations use secrets; +OAuth uses each region's outbound identity. Match key material, not vault URLs or version IDs. | Component | Purpose | |---|---| @@ -65,27 +50,18 @@ outbound managed identity. Match the key material across regions, not the vault | Origin group | Contain the regional Function hostnames and their health, priority, weight, and latency settings. | | Regional origin | Run the same selected implementation and provider integration, with local storage, Key Vault, identity, and telemetry. | -The caller uses `https:///api/SendOtp`, not a list of regional URLs. -Front Door chooses an origin based on health, priority, latency, and weight. Equal priorities -allow both origins to serve traffic. Equal weights do **not** guarantee a 50/50 split. Adding a third region -normally adds an **origin**, not another customer-facing endpoint. +The caller uses `https:///api/SendOtp`. Front Door selects an origin by health, +priority, latency, and weight. Equal weights do not guarantee a 50/50 split. ### Two-region baseline to provision and validate -Use two separate regional stacks, A and B, at equal Front Door priority for active-active service. -Each stack has its own Function App, hosting capacity, private package storage, Key Vault, managed -identities, and telemetry. Both vaults contain the matching decryption key and the provider -credentials needed in that region. Configure provider federation for each regional outbound -identity when required. Verify that each stack can start, resolve keys, refresh credentials, and -serve requests without the other region's storage, vault, or identity. - -Keep one shared Front Door public SendOtp URL and endpoint application registration, with identical -issuer, audience, authorized-caller policy, selected provider route, and encryption public-key -identity across both stacks. Replicate and renew the matching private key securely in each regional -vault using the [key coordination procedure](#coordinate-the-encryption-key). Failover must not -require changing the caller URL, trust configuration, or registered encryption certificate. -Front Door, Entra authentication, and the selected provider remain shared dependencies; regional -duplication does not add provider failover or remove those dependencies. +Use two regional stacks at equal priority. Each has its own Function App, capacity, storage, +Key Vault, identities, telemetry, matching decryption key, and provider credentials. Verify each +can operate without the other region. + +Share the public URL, endpoint app, issuer, audience, caller policy, provider route, and public-key +identity. Replicate the private key using [key coordination](#coordinate-the-encryption-key). +Front Door, Entra authentication, and the provider remain shared dependencies. ## 1. Plan the regional deployment diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index a0884b3..bd4023a 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -4,15 +4,13 @@ [Documentation](README.md) | [Setup reference](../setup/docs/README.md) | [Troubleshooting](../setup/docs/Troubleshooting.md) -Deploy **one endpoint in one Azure region** for Microsoft Entra ID SMS and voice requests. -Guided setup downloads and publishes the selected package without requiring a repository clone -or local build. -SMS and voice can use different provider URLs on the same endpoint; validate each intended channel. +Deploy **one regional endpoint** for Microsoft Entra ID SMS and voice requests. Guided setup +publishes the selected package. Validate each channel you plan to use. Follow in order: [prepare](#1-get-ready), [register](#2-register-your-application), [deploy](#3-run-guided-setup), [connect and test](#4-connect-your-provider-and-test), then [activate and hand over](#5-activate-and-hand-over). -Portal screenshots hide identifying details; labels and layout may differ. +Portal labels may differ from the screenshots. ## 1. Get ready @@ -21,40 +19,32 @@ new application registration and choose a unique resource prefix. This makes the to identify and keeps testing separate from production. Before purchasing or deploying, confirm access to a supported provider's EPP integration through -[Microsoft Security Store](https://securitystore.microsoft.com/private-solutions). Arrange -Microsoft's approved testing and activation procedure with your tenant administrator. -The complete onboarding path includes the provider offer, tenant access, the approved procedure, -and deployment. Confirm each part before continuing, or use the -[access and support guidance](ONBOARDING.md#before-purchasing-or-deploying) to get them in place. +[Microsoft Security Store](https://securitystore.microsoft.com/private-solutions) and arrange +Microsoft's approved testing and activation procedure. See +[access and support](ONBOARDING.md#before-purchasing-or-deploying). -In Security Store, open **Private solutions** and select your approved subscription. -A listing does not guarantee [guided provider support](ONBOARDING.md#provider-credential-names). +In Security Store, open **Private solutions** and select the approved subscription. Check +[guided provider support](ONBOARDING.md#provider-credential-names). Use the [SMS cost estimation guide](COST-CALCULATOR.md) to create a planning estimate, then confirm destination rates, fees, and final costs with your provider. -Have these ready: +Prepare: -- **Provider account:** complete the provider's account and sender registration for your chosen - SMS or voice service. Check [guided provider support](ONBOARDING.md#provider-credential-names). +- **Provider account:** complete account and sender registration for SMS or voice. - **Shell:** Azure Cloud Shell in PowerShell mode, or PowerShell 7+ with an Azure CLI user login. Azure CLI 2.60.0+ is required for Flex Consumption (FC1), or 2.48.1+ for Premium (EP1). Allow access to GitHub, Azure, Microsoft Graph, Key Vault, and the deployment endpoints. C# also needs the .NET 8 SDK and NuGet access. -- **Administrators:** an Azure operator with subscription deployment, resource-provider registration, - and role-assignment permissions; an Entra Privileged Role Administrator for setup; and an - Authentication Policy Administrator for later activation. These are separate permissions. +- **Administrators:** Azure deployment and role-assignment access, a Privileged Role Administrator + for setup, and an Authentication Policy Administrator for activation. - **Azure region and budget:** check availability and subscription quota for your chosen Linux plan. Azure resources and provider services can incur charges. Check the [full prerequisites](../setup/docs/README.md#prerequisites-for-step-2), including the -Microsoft Entra ID allowed-tenants preview. Graph reuses the Azure CLI login; no Graph PowerShell SDK or -separate sign-in is needed. Setup can install Bicep after confirmation, but not Azure CLI. +Microsoft Entra ID allowed-tenants preview. Setup can install Bicep, but not Azure CLI. -Assign an **onboarding owner** to coordinate administrators, provider, and Microsoft support. -Review the [production-readiness considerations](CONTRACT.md#production-limitations) and plan any -capabilities your live design requires, including certification, durable delivery workflows, -duplicate-send protection, and certificate-renewal automation. +Assign an **onboarding owner** and review [production readiness](CONTRACT.md#production-limitations). ## 2. Register your application @@ -72,7 +62,7 @@ In the customer tenant's **Microsoft Entra admin center > App registrations > Ne *Some portal versions label the account type **Single tenant only**. Select the customer directory. The example above is an unsaved form and cannot be reused.* -Use these field labels to find the right IDs: +Use these labels to find the IDs: ![App registration Overview with Application client ID and Directory tenant ID identified for setup and Object ID marked DO NOT USE](images/onboarding/app-registration-overview.png) @@ -82,10 +72,8 @@ The **Object ID** is not either of these setup inputs. All ID values are hidden Let setup add the required API permission and app role. No client secret is needed. See [application registration details](../setup/docs/README.md#step-1---manually-create-the-application). -Also keep your Azure subscription ID handy. The -[setup values worksheet](ONBOARDING.md#inputs-you-supply-to-setup) explains where to find -each value. Use a new application and unique resource prefix for another independent endpoint; -changing the provider or region on a rerun does not create a separate deployment. +Keep the Azure subscription ID handy. The [setup values worksheet](ONBOARDING.md#inputs-you-supply-to-setup) +lists every input. Use a new app and prefix for an independent endpoint. ## 3. Run guided setup @@ -103,50 +91,39 @@ Review the downloaded script, then run it: .\Setup-Epp.ps1 ``` -On a shared or multi-account workstation, use `.\Setup-Epp.ps1 -ForceAuthentication` to request -an explicit Azure CLI device-code sign-in. Microsoft Graph reuses that CLI session. +On a shared workstation, use `.\Setup-Epp.ps1 -ForceAuthentication` for a fresh Azure CLI sign-in. -Select the customer tenant/subscription, the application **client ID** created for this endpoint, -and the Azure region. -Refresh or sign in if the expected account is missing. Then choose: +Select the tenant, subscription, application **client ID**, and region. Then choose: - **Provider and route:** enter or confirm the provider by name. **Tenant scope** means Global/EU provider routing, not Azure region or a data-residency guarantee. -- **One platform:** JavaScript, C#, or Python. Setup verifies, builds as needed, and publishes - the package, with both channel URLs. No channel prompt, local settings, or separate ZIP upload. +- **Platform:** JavaScript, C#, or Python. - **Hosting:** FC1 can scale to zero and cold-start; EP1 has paid warm capacity. FC1's usage grant does not make deployment free. Review [plan details](../setup/docs/README.md#service-plan-selection). - **Resource prefix:** 2-8 lowercase letters/digits, starting with a letter. -Complete any required CLI sign-in, MFA, or administrator consent yourself. Setup checks -prerequisites and prepares the deployment plan; resolve any reported failure before continuing. +Complete required sign-in, MFA, or administrator consent. Resolve preflight errors before approval. -For a new application, setup creates its encryption certificate. If encryption credentials -already exist, follow [certificate selection](../setup/docs/README.md#encryption-certificate-selection) -to reuse one exact Key Vault certificate version or approve replacement. Coordinate maintenance -first: changing these credentials affects every endpoint sharing the application. +For existing encryption credentials, follow +[certificate selection](../setup/docs/README.md#encryption-certificate-selection). Coordinate +changes across every endpoint sharing the application. **Review the complete deployment plan before typing `Yes`.** Check the accounts, region, SMS and voice provider URLs, platform, plan, cache settings, and resource names. Also check -permissions, certificate key IDs, and access to any existing vault. Setup grants the Microsoft -phone-provider service principal Microsoft Graph `Application.Read.All`, a tenant-wide -application-read permission. `No` or Enter cancels deployment. Sign-in and prerequisite prompts -are separate from this approval. +permissions, certificate key IDs, and access to any existing vault. The plan includes Microsoft Graph `Application.Read.All` for the Microsoft phone-provider service +principal. `No` or Enter cancels deployment. -**Plan review without deployment:** run interactively and choose **No** at the final prompt. -This still performs online preflight and downloads. There is no `-DryRun` or `-WhatIf` switch; -see [plan review](../setup/docs/README.md#reviewing-the-plan-without-deploying) for details. +**Plan review:** run interactively and choose **No** at the final prompt. See +[plan review](../setup/docs/README.md#reviewing-the-plan-without-deploying). Wait for **Deployment completed** and the saved summary before Step 4. Setup creates the Function App, storage, Key Vault, identities, and monitoring as one coordinated deployment. Begin without precreating these resources so setup can apply the complete reviewed plan. -Keep `epp-output` private. Its public certificate and -[summary](../setup/docs/README.md#read-the-deployment-summary) identify resources, endpoint, -application client ID, and encryption key ID. No private key is downloaded. +Keep `epp-output` private. Its [summary](../setup/docs/README.md#read-the-deployment-summary) +identifies the resources, endpoint, app, and encryption key. No private key is downloaded. -Setup verifies that the Function can resolve its selected private-key reference before changing -the registered encryption certificates. It also verifies **Easy Auth**, the endpoint's caller-authentication protection, before opening -public access. Keep Easy Auth enabled during setup, testing, and operation. If setup fails, use the +Setup verifies the private-key reference and **Easy Auth** before opening public access. Keep +Easy Auth enabled. If setup fails, use the [troubleshooting guide](../setup/docs/Troubleshooting.md) rather than deleting resources and starting over. To find the deployed resources, open **Azure portal > Resource groups** and select the group @@ -160,8 +137,8 @@ the portal's Create button to deploy the endpoint.* ## 4. Connect your provider and test -Azure deployment is complete. Next, finish -[provider authentication](ONBOARDING.md#complete-provider-authentication). API-key integrations use the +Next, complete [provider authentication](ONBOARDING.md#complete-provider-authentication). +API-key integrations use the [setup-created vault and exact secret names](ONBOARDING.md#where-to-enter-api-key-credentials-in-key-vault). OAuth uses the provider-administrator handoff, not API-key secrets. Keep credentials out of source, screenshots, and shared logs. @@ -175,8 +152,7 @@ In your Function App, open **Settings > Authentication**. Check that authenticat *The **Microsoft** identity provider on this page validates the caller. It is not the phone provider you selected for SMS or voice.* -Ask your onboarding owner to arrange the -[approved deployed-endpoint checks](ONBOARDING.md#validate-the-deployed-endpoint): +Complete the [approved endpoint checks](ONBOARDING.md#validate-the-deployed-endpoint): 1. Confirm that missing or unauthorized caller credentials are rejected. 2. Run an authorized **evaluation** to check authentication and decryption without a provider send. @@ -184,9 +160,6 @@ Ask your onboarding owner to arrange the both provider acceptance and receipt of the SMS or voice call. Acceptance alone is not proof of delivery. 4. Check that the request and relevant logs appear in Application Insights. -The [illustrated monitoring steps](MONITORING.md#find-the-linked-resources-in-the-portal) -show where to open Application Insights and its Logs workspace. - Use Microsoft's approved caller and test procedure for these checks; an ordinary Azure CLI token cannot represent that caller. Activate the endpoint after every required check passes. If a live send times out, investigate its outcome before trying again because the provider may already have @@ -197,8 +170,7 @@ accepted it. Have an **Authentication Policy Administrator** follow the [supported activation procedure](../setup/docs/README.md#step-3---manually-validate-and-activate-policy). They save the existing policy, apply the approved endpoint URL and application client ID, -preserve other settings, and read back and validate the change. Guided setup prepares the endpoint; -the policy administrator completes activation through Microsoft's assisted procedure. +preserve other settings, and validate the result. Repeat for each authentication method you intend to enable. SMS and voice can share the same Function endpoint URL and application client ID; no additional setup run is needed for the second channel. diff --git a/docs/LOGGING.md b/docs/LOGGING.md index 19dc7bd..3cec701 100644 --- a/docs/LOGGING.md +++ b/docs/LOGGING.md @@ -2,12 +2,10 @@ [Documentation](README.md) | [Find deployed logs](MONITORING.md#runtime-specific-log-discovery) | [HTTP contract](CONTRACT.md) -This reference explains endpoint events and how to read them. Start an investigation with the -monitoring walkthrough, then use the runtime format shown here. +Use this reference with the [monitoring walkthrough](MONITORING.md). -**Keep request bodies, decrypted fields, credentials, nonces, raw exceptions, and provider -response bodies out of logs.** Keep SDK, platform, and proxy body tracing off. This page -describes what the application emits; the monitoring pipeline determines what is collected. +**Exclude request bodies, decrypted fields, credentials, nonces, raw exceptions, and provider +responses.** Keep body tracing off. ## Runtime formats @@ -17,13 +15,9 @@ describes what the application emits; the monitoring pipeline determines what is | Python | Standard `logging`; `otp_log.py` defines fixed event IDs, names, levels, and fields. An immutable `LoggerAdapter` supplies Function and Microsoft trace identifiers. | | .NET | Standard `ILogger`; `OtpLog` defines source-generated IDs/names. `ILogger.BeginScope` supplies `FunctionName`, `FunctionRequestId`, `FunctionInvocationId`, `MsClientRequestId`, `MsCorrelationId`, and `MsCorrelationIdSource`. | -Each invocation ends with a fixed `request_completed` event. It is not a changing summary of -every earlier event. -The configured logger/exporter owns formatting and property preservation. -The fields below describe JavaScript, **not portable custom properties**. Python uses `event_name` -and extras such as `httpStatus`; .NET uses `HttpStatus` and scopes such as `FunctionRequestId`. -Python/.NET provider-result events expose fewer fields; do not assume a provider reference ID is -collected. Verify the [deployed schema](MONITORING.md#runtime-specific-log-discovery). +Each invocation ends with `request_completed`. Logger/exporter behavior controls formatting and +property preservation. JavaScript, Python, and .NET field names differ; verify the +[deployed schema](MONITORING.md#runtime-specific-log-discovery). ## Event sequence @@ -46,21 +40,18 @@ A successful live request emits these events in order: | `request_completed` | Final HTTP status, result, and elapsed time; stage-specific fields stay on their events. | JavaScript uses `envelope_validated`; Python/.NET use `payload_validated`. -Evaluation skips provider configuration, credentials, HTTP, and provider events. It emits -`evaluation_completed`, then `response_prepared` and `request_completed`. +Evaluation skips provider work and emits `evaluation_completed`, `response_prepared`, and +`request_completed`. -Failures emit `request_failed` with fixed stage, reason, and HTTP status (`FailureStage`, -`FailureReason`, `HttpStatus` in .NET). Provider results retain coarse `Outcome` and a safe -classification: `provider_http_error`, `provider_rejected`, `unrecognized_provider_status`, +Failures emit `request_failed` with stage, reason, and HTTP status (`FailureStage` and +`FailureReason` in .NET). Provider results use +`Outcome` and a safe classification: `provider_http_error`, `provider_rejected`, `unrecognized_provider_status`, `missing_provider_message_id`, or `invalid_provider_json`. `provider_response_processed` includes that classification, upstream HTTP status, and normalized status, never descriptions/bodies. Credential acquisition/refresh failures use `credential_refresh_failed`; unexpected failures use `unexpected_error`. No event adds credential reads, acquisition, or send retries. -`response_prepared` runs on success **and failure** before the handler returns. It does not prove -host serialization/transmission or caller receipt; use platform request telemetry for transport -completion. A parsed provider rejection uses `provider_response_processed` with a non-success -outcome and fixed reason. +`response_prepared` runs before successful and failed returns; it does not prove caller receipt. `providerHttpStatus` is recorded when response headers arrive. An upstream `200` can precede a body-read timeout: `provider_response_received` may be followed by @@ -70,9 +61,8 @@ Unknown status text and malformed JSON are never logged; malformed JSON emits on ## Safe fields and identifiers -Build metadata from **validated allowlisted fields**, not a body dump with sensitive properties -removed. Unknown properties, `tenantId`, locale/risk data, and decrypted fields stay excluded. -Invalid envelopes never contribute unvalidated type or TTL values. +Build metadata from **validated allowlisted fields**. Exclude unknown, tenant, locale, risk, and +decrypted data. `providerCredentialSource` is `managed_identity_client_assertion` for app tokens and `key_vault` for API keys. Cached resolution does not imply a network fetch or fresh token exchange. diff --git a/docs/MONITORING.md b/docs/MONITORING.md index ac78f80..cebb653 100644 --- a/docs/MONITORING.md +++ b/docs/MONITORING.md @@ -4,13 +4,10 @@ [Documentation](README.md) | [Collection diagnostics](APPLICATION-INSIGHTS.md) | [Log schema](LOGGING.md) | [Endpoint troubleshooting](../setup/docs/Troubleshooting.md#after-deployment-triage) -This guide covers endpoint logs and alert configuration. Guided setup creates Application -Insights and a Log Analytics workspace; the operations team chooses, configures, and tests -notifications. +Guided setup creates Application Insights and Log Analytics. Use this guide to query them and +configure alerts. -For **tenant authentication and potential abuse signals**, use -[Microsoft Entra ID sign-in monitoring](SIGN-IN-MONITORING.md). Endpoint setup does not configure that pipeline; -sign-in failures do not by themselves identify provider failures or prove OTP delivery. +For identity signals, use [Microsoft Entra ID sign-in monitoring](SIGN-IN-MONITORING.md). For a first single-region deployment: @@ -21,14 +18,12 @@ For a first single-region deployment: 3. [Set up and test alerts](#5-set-up-notifications-and-alert-rules), including certificate reminders and cost ownership. -For **missing data**, use [collection checks](#9-sampling-ingestion-gaps-privacy-and-validation). -Additional guidance covers [multiple regions](#3-multiple-regions-and-cross-workspace-queries), +For missing data, use [collection checks](#9-sampling-ingestion-gaps-privacy-and-validation). +See also [multiple regions](#3-multiple-regions-and-cross-workspace-queries), [Workbook](#6-build-a-shared-operations-workbook), [availability checks](#7-availability-authenticated-functions-and-fc1), -and [Front Door](#8-optional-azure-front-door-layer). Start with the single-region steps above. +and [Front Door](#8-optional-azure-front-door-layer). -These examples do not create monitors automatically. Review permissions, privacy, and charges -before applying them. Thresholds and intervals are **starting points, not SLAs**; tune them to -your deployment. +Review permissions, privacy, and charges. Tune sample thresholds to the deployment. ## 1. Record the deployment @@ -44,13 +39,10 @@ Keep a private inventory with one row per expected regional endpoint: | Expected traffic or authorized synthetic-test cadence | Decide whether missing data is actionable, including standby origins. | | Front Door profile, endpoint, origin group, and origin | Associate regional routing and health with the Function. | -Build the expected inventory from the deployment record. Recent telemetry alone can miss an -origin that has not reported. +Build the inventory from the deployment record, not recent telemetry. -[Guided setup](../setup/docs/README.md) creates the collection resources, not action groups, -Workbooks, or alerts. [Verify the deployed collection configuration](APPLICATION-INSIGHTS.md#single-region-configuration); -resource creation alone does not prove ingestion, and other deployment methods can differ. -The template requests 30-day retention; check effective workspace/table retention and policy before changes. +Guided setup does not create action groups, Workbooks, or alerts. Verify +[collection](APPLICATION-INSIGHTS.md#single-region-configuration) and effective retention. ### Portal resources @@ -66,20 +58,15 @@ The template requests 30-day retention; check effective workspace/table retentio ![Application Insights Overview showing the Logs workspace link with account and connection details redacted](images/onboarding/monitoring-workspace.png) -These test-deployment screenshots hide identifiers and connection details. Names, regions, and -layout may differ; navigation links do not prove successful traffic or delivery. +Screenshots hide identifiers. Portal names and layout may differ. -Keep connection strings and credentials private. -`AppRequests`/`AppTraces` are not interchangeable with `FunctionAppLogs` or `AzureDiagnostics`. -Diagnostic export alone does not prove request collection; platform metrics need no log export. +Keep connection strings private. `AppRequests`/`AppTraces` are not interchangeable with +`FunctionAppLogs` or `AzureDiagnostics`. See [pipelines and table names](APPLICATION-INSIGHTS.md#signals-tables-and-metrics-are-different-things). -Use **Log Analytics workspace > Logs** for all KQL below. Application Insights' -application-context query experience can instead use names such as `requests`, -`traces`, `timestamp`, and `cloud_RoleName`; do not mix that schema with these -workspace examples. Replace every angle-bracket placeholder with your private -inventory value before running. `_ResourceId` in these application tables identifies -the **Application Insights component**, not the Function App. +Run the KQL below in **Log Analytics workspace > Logs** and replace every placeholder. +Application Insights uses a different table schema. `_ResourceId` identifies the Application +Insights component, not the Function App. Run this discovery query after a controlled authorized test. The examples use `SendOtp` as the request name; replace that literal everywhere if your observed diff --git a/docs/MULTI-PROVIDER-IMPLEMENTATION.md b/docs/MULTI-PROVIDER-IMPLEMENTATION.md index ca94dbf..34fa0f2 100644 --- a/docs/MULTI-PROVIDER-IMPLEMENTATION.md +++ b/docs/MULTI-PROVIDER-IMPLEMENTATION.md @@ -4,13 +4,11 @@ [Documentation](README.md) | [Normal deployment](GETTING-STARTED.md) | [Provider interface](BRING-YOUR-OWN-PROVIDER.md) -This guide shows how to add controlled primary-to-secondary provider fallback around the existing -`/api/SendOtp` URL. The design adds durable state, provider-specific approval, and dedicated -tests. Keep the provider order on the server for one SMS channel. +Add controlled primary-to-secondary fallback around `/api/SendOtp`. Keep one server-owned SMS +provider order with durable state and provider-specific approval. -Use the secondary **only after confirmed proof that the primary did not accept the request**. -Treat timeouts, lost responses, and generic errors as uncertain outcomes and investigate them -without an automatic resend. +Use the secondary **only after proof that the primary did not accept the request**. Timeouts, +lost responses, and generic errors are uncertain and must not trigger an automatic resend. Follow [policy](#1-set-a-fixed-provider-order-and-a-deny-by-default-policy), [account configuration](#2-configure-and-prevalidate-both-accounts), @@ -23,8 +21,7 @@ Follow [policy](#1-set-a-fixed-provider-order-and-a-deny-by-default-policy), ## 1. Provider order -Keep the existing HTTP registration in [SendOtp.js](../javascript/src/functions/SendOtp.js). -Define the provider order in server-owned startup configuration: +Keep [SendOtp.js](../javascript/src/functions/SendOtp.js) and define the order at startup: ```javascript const providerOrder = Object.freeze({ @@ -34,9 +31,8 @@ const providerOrder = Object.freeze({ }); ``` -Reject unsupported channels and invalid configuration. Do not accept provider IDs, endpoints, -vaults or scopes from request headers or bodies. There is no round-robin selection, fan-out, -recursive fallback or primary retry. The secondary can be attempted at most once. +Reject unsupported channels and request-supplied provider configuration. Do not use round-robin, +fan-out, recursive fallback, or primary retries. Attempt the secondary at most once. Keep **routing order** separate from **fallback eligibility**. Use this policy: @@ -49,20 +45,15 @@ Keep **routing order** separate from **fallback eligibility**. Use this policy: | Primary-only credential acquisition fails before transport is entered | Eligible only if this pre-dispatch fact is recorded, the account policy explicitly permits it, the durable operation guard allows the transition, and the request budget is sufficient. | | Provider-specific, documented proof that the primary did not accept the operation | Eligible only through a reviewed nonacceptance classifier, explicit account policy, the durable guard and sufficient budget. | -**Keep fallback off until the provider confirms a safe nonacceptance signal.** Review that -provider-specific signal and the account policy before enabling the path. An expired or rejected -inbound token never reaches fallback. Provider credential handling continues to honor account -status, consent, access rules, and provider blocks. +**Enable fallback only after the provider confirms a safe nonacceptance signal.** Caller +authentication and provider account controls still apply. ## 2. Configure and prevalidate both accounts [config.js](../javascript/src/functions/config.js) accepts `readConfig(settings)`, so each account can use its own immutable settings without changing `process.env` per request. -The following is a **customer-owned configuration format**, not a file the sample loads. -The account IDs are role labels. Replace each `` with the adapter selected -for that account from the [existing lookup](../javascript/src/functions/providers/index.js); -placeholders are not valid runtime provider names. This example uses API-key authentication; -each account's settings must match its selected adapter's actual credential requirements. +The following **customer-owned format** is not loaded by the sample. Replace each +`` with a registered adapter and match its credential requirements. ```json [ diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index 0cd81fa..32e6c3e 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -2,8 +2,7 @@ [Documentation](README.md) | [First deployment](GETTING-STARTED.md) | [Troubleshooting](../setup/docs/Troubleshooting.md) -Guided setup has already deployed Azure resources and code. Next, complete -[provider authentication](#complete-provider-authentication), then +After deployment, complete [provider authentication](#complete-provider-authentication), then [validate the endpoint](#validate-the-deployed-endpoint). | Task | Go to | @@ -16,28 +15,18 @@ Guided setup has already deployed Azure resources and code. Next, complete ## Before purchasing or deploying -Open [Microsoft Security Store](https://securitystore.microsoft.com/private-solutions) and review -the intended provider offer. Confirm account activation, sender registration, supported countries, -channel, pricing, and access to the provider's **EPP integration**, not just its general messaging API. -An offer purchase does not deploy Azure resources, enable a Microsoft tenant feature, or install -a missing adapter. +In [Microsoft Security Store](https://securitystore.microsoft.com/private-solutions), confirm +account activation, sender registration, countries, channels, pricing, and EPP access. ![Cropped examples of phone-provider offer cards in Microsoft Security Store](images/onboarding/security-store-providers.png) -*These are sample cards, not recommendations or a complete provider list. Offers, preview -labels, pricing, and access can change. A store listing does not mean the setup script supports -that integration; check [guided provider support](#provider-credential-names).* +*Sample cards only. Check [guided provider support](#provider-credential-names).* -Before creating deployment costs, have the tenant administrator confirm the supported EPP -onboarding and activation procedure with Microsoft and the provider. Confirm tenant eligibility, -licensing, preview enrollment, the allowed-tenants preview, an authorized test caller, and the -activation path. Use the offer's support route or the organization's Microsoft support channel -to complete any missing step. Azure subscription ownership is separate from access to these -features. +Before deployment, confirm eligibility, licensing, preview access, an authorized test caller, +and Microsoft's activation procedure. Use the offer's support route or Microsoft support for +missing access. Azure subscription ownership does not grant EPP access. -Assign an **onboarding owner** to coordinate tenant policy, Azure, provider administrators, and -Microsoft support. Get the approved test and policy-change procedures before deployment. Use the -organization's existing support contacts because this repository does not list a named contact. +Assign an **onboarding owner** to coordinate tenant policy, Azure, the provider, and Microsoft support. Use the [production-readiness considerations](CONTRACT.md#production-limitations) to plan the capabilities your live service needs, such as durable delivery, retry and deduplication strategy, @@ -47,9 +36,8 @@ an end-to-end deadline, certificate renewal, and delivery confirmation. ### Inputs you supply to setup -Replace placeholders with values from the named portal pages, not values copied from another tenant. -The [complete noninteractive example](../setup/docs/README.md#noninteractive-example) uses these -same parameters. No parameter accepts a provider API key or private key. +Use values from the named portal pages. The +[noninteractive example](../setup/docs/README.md#noninteractive-example) uses the same parameters. | Input | Where to find or decide it | Where to set it / required for | |---|---|---| @@ -65,32 +53,23 @@ same parameters. No parameter accepts a provider API key or private key. | Existing certificate location | Azure portal > Key Vault > Certificates > certificate > exact version > Certificate Identifier. Use the `/certificates/` URL, not Show Secret Value. | `-KeyVaultCertificateId`; reuse only. Add `-KeyVaultSubscriptionId` when that vault is in a different subscription in the same customer tenant. | | Output directory / versions | A private local folder; optionally a reviewed source commit and published release tag. | `-OutputDirectory`, `-SourceRef`, `-PackageReleaseTag`; optional. Keep launcher and support source aligned. | -**Global/EU is routing metadata, not Azure placement or a residency guarantee.** -[Telesign](../setup/providers/telesign.json) and [Soprano](../setup/providers/soprano.json) currently -use identical Global/EU URLs; Soprano also shares its app ID/scope. Confirm provider processing -locations for messages, credentials, logs, and personal data; choose Azure/telemetry locations separately. +**Global/EU selects provider routing, not Azure placement or residency.** Current Telesign and +Soprano profiles use the same Global/EU URLs; Soprano also shares its app ID and scope. Confirm +provider data locations separately. -**One guided deployment has one provider and both channels.** Validate and activate SMS and voice -separately; a second channel needs no second setup run. The resource suffix uses subscription, -app client ID, and prefix—not provider or region—so changing provider/region with the same IDs can -reconfigure the endpoint. For an independent deployment, use a new app and prefix. Reusing an app -requires an explicit certificate decision and does not isolate other endpoints sharing that app. -The [Front Door design](FRONTDOOR.md) coordinates same-key origins instead of independent setup runs. +**One guided deployment has one provider and both channels.** Validate each channel separately. +Changing provider or region with the same subscription, app, and prefix reconfigures that +endpoint. Use a new app and prefix for an independent endpoint. See [Front Door](FRONTDOOR.md) +for regional origins. ### Values setup configures for you -Review the deployment plan, then retain `deployment-*.json` and the public certificate in -`epp-output` (or your chosen directory). Compare the saved `tenantId`, `subscriptionId`, -`applicationId`, `resources`, `provider`, `endpointRegion`, `providerSmsEndpoint`, and -`providerVoiceEndpoint` with the approved plan. -The [setup output guide](../setup/docs/README.md#read-the-deployment-summary) explains the remaining -fields. `policyChanged: false` is expected, not a failed deployment. - -Inspect cloud settings in **Azure portal > Function App > Settings > Environment variables**. -They are setup-managed; do not recreate them from the local sample or bulk-copy another app's -settings. Do not export all settings into a ticket. Changes can restart the worker, and reruns -can restore setup-managed values. +Keep `deployment-*.json` and the public certificate in private storage. Compare the summary with +the approved plan. `policyChanged: false` is expected. + +Inspect settings in **Function App > Settings > Environment variables**. Keep setup-managed +values out of tickets and avoid copying settings from another app. | Value or setting | Source / verification | Customer action | |---|---|---| @@ -111,15 +90,13 @@ Guided setup writes both channel-specific endpoint settings and requires a packa them. Manual configurations must also supply the endpoint for each intended channel; a missing, empty, or invalid selected endpoint fails closed. -There are **three separate trust paths**: Easy Auth admits the Microsoft caller; the Key Vault -certificate decrypts its JWE; provider credentials authorize outbound delivery. The HTTPS -certificate is separate again. Fix the failing path rather than replacing unrelated IDs or keys. +The trust paths are separate: Easy Auth checks the caller, the Key Vault certificate decrypts +the JWE, and provider credentials authorize delivery. ## Complete provider authentication -Guided setup supports **Soprano and Telesign**. The other bundled adapters are available for -custom development and deployment. The table is alphabetical, not a recommendation order: +Guided setup supports **Soprano and Telesign**. Other bundled adapters require custom deployment. | Provider | Credential names / mode | Deployment support | |---|---|---| @@ -130,7 +107,7 @@ custom development and deployment. The table is alphabetical, not a recommendati ### Where to enter API-key credentials in Key Vault -Use the vault created for **your endpoint**, not a vault chosen from a screenshot: +Use the vault named in the deployment summary: 1. Open the saved deployment summary and find `resources.keyVault`. 2. In **Azure portal > Key vaults**, open that vault. @@ -139,9 +116,7 @@ Use the vault created for **your endpoint**, not a vault chosen from a screensho ![Key Vault Objects menu with Secrets selected and the Generate/Import action visible](images/onboarding/key-vault-secrets.png) -*This existing test vault contains examples for more than one adapter. Create only the secrets -required by your chosen integration in the table above. The screenshot does not mean guided -setup configures multiple providers or supports every adapter.* +*The screenshot contains test examples. Create only the required secrets.* 4. Choose **Manual**. For **Name**, use the exact name in the provider table above. For **Secret value**, paste the credential obtained through your provider's secure process. @@ -151,15 +126,10 @@ setup configures multiple providers or supports every adapter.* ![Unsaved Create a secret form showing a sample name, masked placeholder value, and Enabled set to Yes](images/onboarding/key-vault-create-secret.png) -*The screenshot uses `example-provider-api-key` and a fake masked value to show the form. -Neither is a working configuration. The form was cancelled without saving. Use the exact -provider-specific secret names, not this example name, and never reveal a real key for a screenshot.* +*Use the exact provider secret name. The shown value is fake.* -After saving, verify the name, enabled state, and version metadata without selecting -**Show Secret Value**. Do not edit or replace the setup-created encryption certificate secret. -If the vault denies access, ask the approved credential administrator for help; do not disable -vault protection or copy credentials into Function settings. See the permission and runtime -checks in the provider-specific steps below. +After saving, verify the name, enabled state, and version without viewing the value. Keep the +setup-created encryption certificate unchanged. ### Telesign credentials diff --git a/docs/README.md b/docs/README.md index 98a853f..d6283b8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,9 +2,8 @@ [Sample overview and architecture](../README.md) -**First deployment:** follow [Get started](GETTING-STARTED.md). Confirm supported access and -Microsoft's approved testing and activation process first. Build the endpoint in a separate -nonproduction environment, validate it, and then complete policy activation. +**First deployment:** follow [Get started](GETTING-STARTED.md). Confirm access and Microsoft's +approved test and activation process before deploying in a nonproduction environment. ## Table of Contents @@ -17,10 +16,10 @@ nonproduction environment, validate it, and then complete policy activation. ## Understand the sample -- **Architecture and implementation choices:** [Technical reference](../TECHNICAL.md) -- **Requests, responses, settings, and required behavior:** [HTTP contract](CONTRACT.md) -- **Runtime events and safe diagnostic fields:** [Application log reference](LOGGING.md) -- **Security and production planning:** [Production-readiness considerations](CONTRACT.md#production-limitations) +- [Architecture and implementation](../TECHNICAL.md) +- [HTTP contract](CONTRACT.md) +- [Log schema](LOGGING.md) +- [Production readiness](CONTRACT.md#production-limitations) ## Deploy and validate @@ -34,34 +33,28 @@ nonproduction environment, validate it, and then complete policy activation. ## Operate -- **Function logs and alerts:** [Monitoring](MONITORING.md) -- **Missing or incomplete telemetry:** [Application Insights collection](APPLICATION-INSIGHTS.md) -- **Microsoft Entra ID sign-ins and identity alerts:** [Identity monitoring](SIGN-IN-MONITORING.md) -- **Certificate renewal:** [Certificate lifecycle](../setup/docs/README.md#encryption-certificate-lifecycle) -- **Rollback or decommissioning:** [Rollback procedure](../setup/docs/README.md#rollback-and-decommissioning) +- [Function logs and alerts](MONITORING.md) +- [Application Insights collection](APPLICATION-INSIGHTS.md) +- [Microsoft Entra ID monitoring](SIGN-IN-MONITORING.md) +- [Certificate renewal](../setup/docs/README.md#encryption-certificate-lifecycle) +- [Rollback and decommissioning](../setup/docs/README.md#rollback-and-decommissioning) ## Develop -- **Runtime development:** [JavaScript](../javascript/README.md), [C#](../dotnet/README.md), or [Python](../python/README.md) -- **Settings and packages:** [Technical reference](../TECHNICAL.md) -- **Requests and responses:** [HTTP contract](CONTRACT.md) -- **Runtime events:** [Application log schema](LOGGING.md) -- **Provider integration:** [Provider-neutral BYOT interface](BRING-YOUR-OWN-PROVIDER.md) -- **Production planning:** [Readiness considerations](CONTRACT.md#production-limitations) +- Runtimes: [JavaScript](../javascript/README.md), [C#](../dotnet/README.md), [Python](../python/README.md) +- [Settings and packages](../TECHNICAL.md) +- [HTTP contract](CONTRACT.md) +- [Log schema](LOGGING.md) +- [Phone-service integration](BRING-YOUR-OWN-PROVIDER.md) ## Advanced topics -Use these guides after validating a single-region endpoint. Each one includes implementation, -validation, operations, and rollback guidance. - -- **Route across regions:** [Multi-region Front Door design](FRONTDOOR.md) -- **Add provider fallback:** [Multi-provider implementation](MULTI-PROVIDER-IMPLEMENTATION.md) +- [Multi-region routing](FRONTDOOR.md) +- [Provider fallback](MULTI-PROVIDER-IMPLEMENTATION.md) ## Get help -For access, approved testing, or activation, use the -[provider/Microsoft support route](ONBOARDING.md#before-purchasing-or-deploying). -For setup or runtime problems, start with the [troubleshooting routes](../setup/docs/Troubleshooting.md). -Keep credentials, phone data, and raw payloads out of support requests; use -[safe diagnostic evidence](../setup/docs/Troubleshooting.md#after-deployment-triage). +- [Provider access, testing, and activation](ONBOARDING.md#before-purchasing-or-deploying) +- [Troubleshooting](../setup/docs/Troubleshooting.md) +- [Safe diagnostic evidence](../setup/docs/Troubleshooting.md#after-deployment-triage) diff --git a/docs/SIGN-IN-MONITORING.md b/docs/SIGN-IN-MONITORING.md index 128ff10..ed42887 100644 --- a/docs/SIGN-IN-MONITORING.md +++ b/docs/SIGN-IN-MONITORING.md @@ -9,18 +9,11 @@ | Diagnose missing records or quiet alerts | [Check data](#4-check-your-data-and-choose-a-query), [monitor ingestion](#monitor-ingestion-separately) | | Adapt a query or add advanced alerts | [Advanced monitoring reference](#advanced-monitoring-reference) | -The six-step walkthrough is a worked example for one user sign-in alert. Each additional monitor -requires its own scope, baseline, and query. - -This guide covers Microsoft Entra authentication and configuration. Function execution and phone -delivery have separate monitoring paths. Identity details remain private; administrators configure -the export and alerts after endpoint setup. +The six-step walkthrough creates one user sign-in alert. Function and delivery monitoring are separate. ## Recommended monitors -Agree on the source tenant, exact identity and resource IDs, and notification owners. Then -implement the monitors in priority order. Use exact IDs instead of name fragments or every -tenant application. +Choose the tenant, exact identity/resource IDs, and notification owners. Use exact IDs. ### Integration sign-in failures @@ -28,15 +21,10 @@ tenant application. 1. **Watch:** application/service-principal and managed-identity authentication in **AADServicePrincipalSignInLogs** and **AADManagedIdentitySignInLogs**. Filter to the known app/service principal **and** intended resource in the source tenant. -2. **Notify:** when failures repeat above the usual level for the same observation window, - or a new error starts recurring. Group by identity, resource, and error; show the last - successful sign-in alongside failures. Start with a review notification, then agree - with the owner which sustained failures need paging. -3. **Owner and first checks:** the Azure/identity integration operator checks the error, - identity status, credential validity, federation settings, and recent permission changes. - Token success does not prove a later API operation is authorized. Token caching means - there is not a sign-in event for every service request: only alert on absent successes - after accounting for the expected schedule, caching, and [collection health](#monitor-ingestion-separately). +2. **Notify:** when failures exceed the normal level or a new error recurs. Group by identity, + resource, and error; include the last successful sign-in. +3. **First checks:** review the error, identity, credentials, federation, permissions, expected + schedule, token caching, and [collection health](#monitor-ingestion-separately). ### Credential and permission changes @@ -46,14 +34,10 @@ tenant application. service-principal changes, consent, and permission grants/removals. Have the maintainer verify which [audit activities](https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-audit-activities#core-directory) actually appear for these operations. -2. **Notify:** on an unexpected **successful** change to those targets; one can deserve - review without a volume threshold. Keep failed attempts separate from completed changes. - Record approved maintenance so planned changes do not page as suspected abuse. -3. **Owner and first checks:** the application/identity administrator checks who changed - what, when, and against which approved change ticket. Review target and changed-property - names; link to restricted logs instead of attaching raw old/new values. Group related - records before escalating an unexplained change to security. This is change monitoring, - not a credential-expiry inventory or automatic revocation rule. +2. **Notify:** on an unexpected **successful** change. Keep failed attempts separate and record + approved maintenance. +3. **First checks:** review the actor, target, time, approved change, and changed-property names. + Link to restricted logs instead of copying raw values. ### Repeated user sign-in failures @@ -61,20 +45,14 @@ tenant application. 1. **Watch:** **SigninLogs** for the customer-facing app and source tenant. Review **AADNonInteractiveUserSignInLogs** separately for background token/session problems; do not mix those records into the interactive failure count or rate. -2. **Notify:** when the same error keeps recurring or unresolved failures rise above - ordinary traffic for the agreed window. Start with counts; use a rate only with a - matching population and minimum traffic level. A consent prompt or expired session - can recover, so check later outcomes before treating it as a continuing problem. -3. **Owner and first checks:** identity support reads the overall error and reason, - then checks recent app/policy changes and related recovery records. Successful - Conditional Access or MFA steps do not make the overall sign-in successful. - Escalate to security only with corroborating signals, not a nonzero code alone. - -**Set up once, then adapt:** select the [required categories](#choose-additional-export-categories), -verify [fields](#field-checks-for-the-recommended-monitors), and reuse the -[alert setup](#5-create-your-first-alert) and [safe test](#6-test-then-hand-over). -Choose each monitor's threshold from normal traffic for that environment rather than a small -export or a universal SLA. +2. **Notify:** when an error recurs or unresolved failures exceed the baseline. Check later + outcomes because consent prompts and expired sessions can recover. +3. **First checks:** review the overall error, recent app or policy changes, and recovery records. + Successful MFA steps do not make the overall sign-in successful. + +Select the [required categories](#choose-additional-export-categories), verify +[fields](#field-checks-for-the-recommended-monitors), and reuse the +[alert setup](#5-create-your-first-alert). Set thresholds from normal traffic. ## 1. Choose your tenant and access @@ -83,11 +61,10 @@ export or a universal SLA. from the tenant hosting the Function. Note its **Tenant ID** from **Overview**. 2. Ask your tenant administrator for **Reports Reader**, the minimum role for viewing activity logs. See Microsoft's [access requirements](https://learn.microsoft.com/en-us/entra/identity/monitoring-health/howto-access-activity-logs#prerequisites). - This role does not grant access to an Azure workspace (where exported logs are stored). + This role does not grant workspace access. 3. Check whether you have a **workforce tenant** or an **external tenant** for customer apps. - If unsure, use the [tenant comparison](https://learn.microsoft.com/en-us/entra/external-id/customers/concept-supported-features-customers) - with your administrator. Both can use the portal investigation below, but external - tenants need a different export setup in Step 3. + Use the [tenant comparison](https://learn.microsoft.com/en-us/entra/external-id/customers/concept-supported-features-customers) + if needed. External tenants use a different export path in Step 3. ## 2. Find one sign-in diff --git a/dotnet/README.md b/dotnet/README.md index 3591d85..f5ddd51 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -4,36 +4,29 @@ [Documentation](../docs/README.md) | [Settings](#environment-configuration) | [Request behavior](#request-behavior) | [Source](#source) -Implements the shared [contract](../docs/CONTRACT.md) with one `SendOtp` request pipeline and one -selected provider per deployment. Target: .NET 8 isolated worker, Azure Functions v4. +Implements the shared [contract](../docs/CONTRACT.md) with one provider per deployment. +Target: .NET 8 isolated worker, Azure Functions v4. ## Setup and deployment -**Sample deployment:** use the [five-step guide](../docs/GETTING-STARTED.md). -Guided setup builds with the .NET 8 SDK and publishes the Function. Next, complete +**Sample deployment:** follow the [five-step guide](../docs/GETTING-STARTED.md), then complete [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). -The [guided provider list](../docs/ONBOARDING.md#provider-credential-names) covers a subset of -the adapters in this folder. **Custom package development:** follow these steps from `dotnet/`: -1. Create the [local settings below](#environment-configuration). For live integration, choose an - adapter's fixed `Name` from [Providers/](Src/Providers/), configure its endpoints/options, and - complete [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). - Select one provider explicitly. +1. Create the [local settings below](#environment-configuration). Select an adapter's fixed `Name` + from [Providers/](Src/Providers/), configure it, and complete + [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). 2. Build [dotnet.csproj](dotnet.csproj), run [xUnit tests](tests/Epp.Otp.Tests.csproj), and start Core Tools here, **on loopback only, without tunnels**; it has no Easy Auth. -3. For manual deployment, publish this app to a compatible .NET isolated Function App. - Inspect the output and [.funcignore](.funcignore); the project excludes local settings, and - private keys must not be packaged. Complete the - [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist) before public - access. Keep Easy Auth enabled for SendOtp, then run +3. For manual deployment, publish to a compatible .NET isolated Function App. Inspect the output + for private settings and complete the + [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist), then run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint) to verify platform authentication. -**Regional continuity:** use the [Front Door guide](../docs/FRONTDOOR.md) to implement and validate -the readiness route and regional deployment for .NET. The recorded trials used JavaScript, so -include .NET-specific package, startup, dependency, and telemetry checks. +**Regional continuity:** use the [Front Door guide](../docs/FRONTDOOR.md) and include +.NET-specific package, startup, dependency, and telemetry checks. ## Environment configuration @@ -59,10 +52,9 @@ Replace placeholders and store credentials in the adapter's Key Vault secrets. E channel requires a valid endpoint; an invalid or missing endpoint returns an error without using the other channel. -Core Tools loads `Values` into environment variables; direct worker execution and unit tests do not -load local settings. [AppConfig.Read](Src/AppConfig.cs) reads them through `IEnv`. Restart after -edits. Core Tools cannot resolve Key Vault references locally, so use only a local test PEM or -base64 PEM. The [project](dotnet.csproj) excludes local settings from publish output. +Core Tools loads `Values` into environment variables; direct worker execution and tests do not. +Restart after edits. Use a local test PEM because Core Tools cannot resolve Key Vault references. +The [project](dotnet.csproj) excludes local settings from publish output. In Azure, configure the same app settings and use a Key Vault reference for the private PEM. Provider credentials use managed identity, not a developer CLI login. Keep local evaluation on loopback. @@ -79,10 +71,9 @@ enum tokens, or other deserialization errors can fail before `SendOtp`, its `ILo or its custom error response. Successfully bound requests retain application validation, privacy-safe logging, and response behavior. See [binding differences](../docs/CONTRACT.md#runtime-binding-differences). -Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) for no-send validation: -authentication and decryption still run, but request-path provider lookup, credentials, and HTTP do not. -Live success waits for acceptance, **not handset receipt**; failures omit the nonce. -Treat a timeout as uncertain; check the provider outcome before deciding whether to retry. +Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) to validate +authentication and decryption without a provider send. Live success confirms acceptance, +**not handset receipt**. Check a timeout before retrying. For message/voice mapping, see the [Soprano delivery contract](../docs/CONTRACT.md#encrypteddeliverycontext-jwe) and [Telesign integration](../TECHNICAL.md#telesign-epp). diff --git a/javascript/README.md b/javascript/README.md index 777d3c2..d903412 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -4,35 +4,28 @@ [Documentation](../docs/README.md) | [Settings](#environment-configuration) | [Request behavior](#request-behavior) | [Source](#source-and-extension-points) -A Node.js Azure Function implementing the shared [HTTP contract](../docs/CONTRACT.md). -One configured provider owns its credentials, request mapping, and response interpretation. +A Node.js Azure Function implementing the shared [HTTP contract](../docs/CONTRACT.md) with one +provider per deployment. ## Setup -**Sample deployment:** use the [five-step guide](../docs/GETTING-STARTED.md). -After guided setup, continue with +**Sample deployment:** follow the [five-step guide](../docs/GETTING-STARTED.md), then complete [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). -The [guided provider list](../docs/ONBOARDING.md#provider-credential-names) covers a subset of -the adapters in this folder. **Custom package development:** follow these steps from `javascript/`: -1. Create the [local settings below](#environment-configuration). For live integration, choose a - fixed provider ID from [providers/](src/functions/providers/), configure its endpoints/options, - and complete [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). - Select one provider explicitly. +1. Create the [local settings below](#environment-configuration). Select a fixed provider ID from + [providers/](src/functions/providers/), configure it, and complete + [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). 2. Install [package.json](package.json) dependencies, run its offline test script, and start Core Tools from this folder. **Bind only to loopback, without tunnels:** Core Tools has no Easy Auth. -3. For manual deployment, [package](../TECHNICAL.md#build-zips-locally) this app only, preserving - dependencies and [.funcignore](.funcignore). Inspect for secrets before upload. Complete the - [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist) before public - access. Keep Easy Auth enabled for SendOtp, then run +3. For manual deployment, [package](../TECHNICAL.md#build-zips-locally) this app, inspect it for + secrets, and complete the [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist). + Keep Easy Auth enabled for SendOtp, then run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint) to verify platform authentication. -**Regional continuity:** [Front Door](../docs/FRONTDOOR.md) uses multiple regional origins behind -one URL. Its JavaScript trials and readiness contract provide the starting point for implementing -and validating your regional package and provisioning process. +**Regional continuity:** use the [Front Door guide](../docs/FRONTDOOR.md). ## Environment configuration @@ -58,10 +51,9 @@ Keep `Values` as strings, including `EPP_PROVIDER_TIMEOUT_MS`, for example `"150 Replace placeholders; Store API keys in Key Vault. Each selected channel requires a valid endpoint; an invalid or missing endpoint returns an error without using the other channel. -Core Tools copies `Values` into the process environment; direct Node processes and offline tests do -not load this file. [AppConfig](src/functions/config.js) reads `process.env` when `readConfig()` runs. -Restart after edits. Core Tools cannot resolve Key Vault references locally, so use a local -test PEM or base64 PEM. Keep Azurite settings in the local environment. +Core Tools copies `Values` into the process environment; direct Node processes and offline tests +do not load the file. Restart after edits. Use a local test PEM because Core Tools cannot resolve +Key Vault references locally. Keep Azurite settings local. In Azure, configure the same app settings and use a Key Vault reference for the private PEM. Provider secrets use managed identity, not a developer CLI login. Keep local evaluation on loopback. @@ -73,10 +65,9 @@ Easy Auth authenticates the caller. JWE decryption, nonce values, and request fi parts of the contract. The handler leaves incoming `Authorization` processing to Easy Auth and does not echo it. -Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) for no-send validation: -authentication and decryption still run, but request-path provider lookup, credentials, and HTTP do not. -Live success waits for acceptance, **not handset receipt**; failures omit the nonce. -Treat a timeout as uncertain; check the provider outcome before deciding whether to retry. +Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) to validate +authentication and decryption without a provider send. Live success confirms acceptance, +**not handset receipt**. Check a timeout before retrying. For message/voice mapping and credential rules, see the [Soprano delivery contract](../docs/CONTRACT.md#encrypteddeliverycontext-jwe) and @@ -89,19 +80,16 @@ Use the provider-neutral caller authentication, and response rules. Customers can connect the outbound phone service that meets their requirements. -For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and the -[JavaScript service-event queries](../docs/MONITORING.md#4-javascript-service-event-queries). -Logs are JSON `service` events ending in `request_completed`, not a `logType: "request"` summary. +For diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and the +[JavaScript event queries](../docs/MONITORING.md#4-javascript-service-event-queries). The [cache switches and defaults](../docs/CONTRACT.md#credential-caching-and-refresh) apply here. Disabling the selected cache removes startup preparation, polling, and cross-request reuse, not live credential acquisition. -With caching enabled, startup selects only the provider's API-key or OAuth cache. API keys use -Key Vault and `lru-cache`; OAuth uses the managed-identity/Entra SDKs. A shared 30-second refresh -loop and one in-flight acquisition keep warm reads nonblocking; failures never extend expiry. -See the [refresh contract](../docs/CONTRACT.md#credential-caching-and-refresh). Leave the provider -unset for local evaluation without credential acquisition; prewarming never sends an OTP. +With caching enabled, API keys use Key Vault and `lru-cache`; OAuth uses the managed-identity/Entra +SDKs. A 30-second refresh loop and one in-flight acquisition support warm reads. See the +[refresh contract](../docs/CONTRACT.md#credential-caching-and-refresh). | Source | Purpose | |---|---| diff --git a/python/README.md b/python/README.md index 70e0e42..7e4b0b9 100644 --- a/python/README.md +++ b/python/README.md @@ -4,35 +4,29 @@ [Documentation](../docs/README.md) | [Settings](#environment-configuration) | [Request behavior](#request-behavior) | [Source](#source) -Implements the shared [contract](../docs/CONTRACT.md) with a direct Azure Function flow and one -selected provider per deployment. Target: Python 3.11, Azure Functions v4, Python v2 programming model. +Implements the shared [contract](../docs/CONTRACT.md) with one provider per deployment. +Target: Python 3.11, Azure Functions v4, Python v2 programming model. ## Setup and deployment -**Sample deployment:** use the [five-step guide](../docs/GETTING-STARTED.md). -Guided setup builds remotely and publishes the Function without requiring a local Python install. -Next, complete [provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). -The [guided provider list](../docs/ONBOARDING.md#provider-credential-names) covers a subset of -the adapters in this folder. +**Sample deployment:** follow the [five-step guide](../docs/GETTING-STARTED.md), then complete +[provider authentication and validation](../docs/ONBOARDING.md#complete-provider-authentication). **Custom package development:** follow these steps from `python/`: -1. Create the [local settings below](#environment-configuration). For live integration, choose a - fixed provider ID from [providers/](src/providers/), configure its endpoints/options, and - complete [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). - Select one provider explicitly. +1. Create the [local settings below](#environment-configuration). Select a fixed provider ID from + [providers/](src/providers/), configure it, and complete + [provider authentication](../docs/ONBOARDING.md#complete-provider-authentication). 2. Use a virtual environment, install [requirements.txt](requirements.txt) and pytest, and run [tests/](tests/). Start Core Tools here, **on loopback only, without tunnels**; it has no Easy Auth. -3. For manual deployment, publish this app to a compatible Linux Python Function with dependencies - or a supported remote build. Inspect [.funcignore](.funcignore) and the package for private - settings/keys. Complete the [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist) - before public access. Keep Easy Auth enabled for SendOtp, then run +3. For manual deployment, publish to a compatible Linux Python Function with dependencies or + remote build. Inspect the package for private settings, complete the + [Easy Auth trust checklist](../docs/ONBOARDING.md#manual-deployment-trust-checklist), and run [authorized deployed checks](../docs/ONBOARDING.md#validate-the-deployed-endpoint) to verify platform authentication. -**Regional continuity:** use the [Front Door guide](../docs/FRONTDOOR.md) to implement and validate -the readiness route and regional deployment for Python. The recorded trials used JavaScript, so -include Python-specific package, startup, dependency, and telemetry checks. +**Regional continuity:** use the [Front Door guide](../docs/FRONTDOOR.md) and include +Python-specific package, startup, dependency, and telemetry checks. ## Environment configuration @@ -57,10 +51,9 @@ Keep values as strings, including `EPP_PROVIDER_TIMEOUT_MS: "1500"`. Replace pla Store API keys in provider-named Key Vault secrets. Each selected channel requires a valid endpoint; an invalid or missing endpoint returns an error without using the other channel. -Core Tools loads `Values` into `os.environ`; direct Python execution and pytest do not. -[read_config](src/config.py) returns an `AppConfig`, and the handler reads its attributes. -Restart after edits. Core Tools cannot resolve Key Vault references locally, so use a local -test PEM or base64 PEM. Keep Azurite settings in the local environment. +Core Tools loads `Values` into `os.environ`; direct Python and pytest do not. Restart after edits. +Use a local test PEM because Core Tools cannot resolve Key Vault references locally. Keep Azurite +settings local. In Azure, configure the same app settings and use a Key Vault reference for the private PEM. Provider secrets use managed identity, not a developer CLI login. Keep local evaluation on loopback. @@ -72,10 +65,9 @@ Easy Auth authenticates the caller. JWE decryption, nonce values, and request fi parts of the contract. The handler leaves incoming `Authorization` processing to Easy Auth and does not echo it. -Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) for no-send validation: -authentication and decryption still run, but request-path provider lookup, credentials, and HTTP do not. -Live success waits for acceptance, **not handset receipt**; failures omit the nonce. -Treat a timeout as uncertain; check the provider outcome before deciding whether to retry. +Use [`mode: 2` or `"evaluation"`](../docs/CONTRACT.md#evaluation-no-provider-send) to validate +authentication and decryption without a provider send. Live success confirms acceptance, +**not handset receipt**. Check a timeout before retrying. For message/voice mapping, see the [Soprano delivery contract](../docs/CONTRACT.md#encrypteddeliverycontext-jwe) and [Telesign integration](../TECHNICAL.md#telesign-epp). @@ -87,20 +79,17 @@ Use the provider-neutral caller authentication, and response rules. Customers can connect the outbound phone service that meets their requirements. -For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and +For diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and [runtime-specific log discovery](../docs/MONITORING.md#runtime-specific-log-discovery). -Python emits fixed messages with `event_name` and other logging extras, not a universal JSON -request summary. Verify which properties the host/export pipeline preserves. +Verify which Python logging properties the host/export pipeline preserves. The [cache switches and defaults](../docs/CONTRACT.md#credential-caching-and-refresh) apply here. Disabling the selected cache removes startup preparation, polling, and cross-request reuse, not live credential acquisition. -With caching enabled, startup selects only the provider's API-key or OAuth cache. API keys use -Key Vault and `cachetools.TTLCache`; OAuth uses the managed-identity/Entra SDKs. A daemon polls -every 30 seconds; synchronous SDK calls use connect/read timeouts rather than one total deadline. -See the [refresh contract](../docs/CONTRACT.md#credential-caching-and-refresh). Leave the provider -unset for local evaluation without background credential acquisition. +With caching enabled, API keys use Key Vault and `cachetools.TTLCache`; OAuth uses the +managed-identity/Entra SDKs. A daemon polls every 30 seconds. See the +[refresh contract](../docs/CONTRACT.md#credential-caching-and-refresh). | Source | Purpose | |---|---| diff --git a/setup/docs/README.md b/setup/docs/README.md index ef19361..b1ac358 100644 --- a/setup/docs/README.md +++ b/setup/docs/README.md @@ -15,11 +15,10 @@ five-step guide above; it handles the required source and Bicep files. | Validate, activate, and maintain | [Step 3](#step-3---manually-validate-and-activate-policy), [renewal](#encryption-certificate-lifecycle), [rollback](#rollback-and-decommissioning) | | Develop setup changes | [Offline checks](#offline-setup-checks) | -Confirm [provider access and the approved Microsoft procedure](../../docs/ONBOARDING.md#before-purchasing-or-deploying) -before purchasing or deploying. A provider purchase alone does not enable the tenant feature or authorize its API. +Confirm [provider access and Microsoft's approved procedure](../../docs/ONBOARDING.md#before-purchasing-or-deploying) +before deployment. -Setup creates **one endpoint in one Azure region**. Regional continuity uses the -[Front Door guide](../../docs/FRONTDOOR.md) to coordinate origins and encryption keys. +Setup creates **one regional endpoint**. See [Front Door](../../docs/FRONTDOOR.md) for regional continuity. ## Availability @@ -27,18 +26,16 @@ Choose **Telesign or Soprano**, **Global/EU** provider routing (prompt: **Tenant **Node.js/.NET/Python**, and **FC1/EP1** hosting. Setup verifies `SHA256SUMS.txt`, builds as needed, and publishes; no package URL/hash input is needed. See [source/package selection](#source-versioning). -Both SMS and voice are configured. Request `channel` selects the URL; there is no setup channel -prompt or app-setting restriction. Profile objects `deployment.routes.global` and -`deployment.routes.eu` contain `smsEndpoint`, `voiceEndpoint`, tenant, authentication, and timing; -Soprano also supplies its API app ID/scope. Setup writes the URLs to -`EPP_PROVIDER_SMS_ENDPOINT` and `EPP_PROVIDER_VOICE_ENDPOINT`; they can differ. +Both channels are configured. Profile objects `deployment.routes.global` and +`deployment.routes.eu` define `smsEndpoint`, `voiceEndpoint`, tenant, authentication, and timing +values. Setup writes the endpoints to `EPP_PROVIDER_SMS_ENDPOINT` and +`EPP_PROVIDER_VOICE_ENDPOINT`; Soprano also supplies its API app ID and scope. -Use a Function package that supports these channel-specific settings. Both URLs are configured -explicitly; neither channel uses a shared endpoint fallback. Older packages must be upgraded. +Use a package that supports channel-specific settings; neither channel falls back to the other URL. The current profiles use identical Global/EU URLs, and Soprano uses the same app ID/scope. -This label is not proof of data residency or a choice of Azure region; confirm processing/routing -with the provider. Infobip/Sinch are bundled adapters but have no guided deployment profiles. +The route label is not a residency or Azure-region selection. Infobip and Sinch are bundled +adapters without guided profiles. ## Service plan selection @@ -49,65 +46,51 @@ Setup offers these two Linux hosting plans, with the same cache app settings for | 1. Flex Consumption (FC1) | On-demand with a free usage grant; zero always-ready instances, 2048 MB, up to 40 on-demand instances | `"false"` | `"false"` | | 2. Premium (EP1) | Existing Premium configuration with one warm instance | `"true"` | `"true"` | -**FC1 is not an always-free deployment.** Its on-demand compute has a +FC1 on-demand compute has a [free usage grant](https://learn.microsoft.com/azure/azure-functions/flex-consumption-plan#billing); usage beyond the grant, Key Vault, storage, telemetry, and provider services can incur charges. -FC1 can scale to zero and cold-start; these settings do not remove cold-start latency or force a -new worker on every invocation. +FC1 can scale to zero and cold-start. -EP1 allocates paid warm capacity even without OTP traffic. Set an Azure Cost Management budget -and notify the resource owner; a budget notification does not stop spending. Include storage, -Key Vault, telemetry ingestion/retention, and the provider's charges, not just Function executions. -Stopping a Function does not necessarily stop its plan or supporting-service charges. +EP1 allocates paid warm capacity without OTP traffic. Budget for the plan, storage, Key Vault, +telemetry, and provider services. Budget alerts do not stop spending. -**Deploy a Function package that supports these switches.** Older packages ignore them. -Each switch independently controls its provider credential cache; unset defaults to `true`. -With the selected cache disabled, live requests acquire credentials on demand without startup -preparation, polling, or cross-request reuse. Values must be trimmed, case-insensitive `true` or -`false`; restart after edits. Rerunning setup restores the selected plan's values. -The decryption-key Key Vault reference remains platform-managed. +Each switch controls one provider credential cache; unset defaults to `true`. Disabled caches +acquire credentials per live request. Values are trimmed, case-insensitive `true` or `false`; +restart after edits. Rerunning setup restores the plan defaults. Older packages ignore these switches. -Use `-ServicePlan FC1` or `-ServicePlan EP1` to skip the prompt. Unattended setup requires this -parameter; it never silently chooses a paid plan. The approval and deployment summary include -the selected plan and both cache settings. +Use `-ServicePlan FC1` or `-ServicePlan EP1` to skip the prompt. Unattended setup requires it. **Use a different resource prefix to change plans.** In-place FC1/EP1 migration is not supported. Preflight rejects a mismatch with the existing resource-group tag or actual hosting-plan SKU, including older EP1 deployments without the new tag. Existing EP1 deployments can be rerun with `-ServicePlan EP1`. -For an active endpoint, onboard a new dedicated Step 1 application as well, or coordinate an -encryption-key continuity migration with its owner. A new prefix creates a new vault, but reuse of -the application still requires an explicit certificate decision. Select the existing certificate -and its Key Vault location to retain key continuity; replacement affects all endpoints sharing the -application. Setup does not automatically migrate keys or activate the replacement endpoint. +For an active endpoint, use a new Step 1 app or coordinate key continuity. Reusing an app still +requires an explicit certificate choice; replacement affects every endpoint sharing the app. ## Step 1 - manually create the application -For the first deployment, choose a nonproduction tenant and subscription where testing is isolated -from production. +For the first deployment, use a nonproduction tenant and subscription. 1. In the customer tenant's **Microsoft Entra admin center > App registrations**, register a new application for this endpoint with **Accounts in this organizational directory only** as its initial supported account type; setup changes it to organizational multi-tenant after approval. - No redirect URI, client secret, API permission, app role, - or enterprise-application configuration is required manually. + No redirect URI, client secret, API permission, or app role is required manually. 2. Record the **Directory (tenant) ID** and **Application (client) ID**. The script requires the client ID, not the application's object ID, and will not create a replacement registration. -3. Complete provider purchase and account/sender registration, and arrange API onboarding for the selected adapter. +3. Complete provider purchase, sender registration, and API onboarding. Telesign uses `telesign-api-key` and `telesign-customer-id` in Key Vault. Soprano uses OAuth client-assertion exchange with the selected provider tenant/scope/application ID. Setup does not grant provider API consent or application roles. Finish the [provider authentication handoff](../../docs/ONBOARDING.md#complete-provider-authentication) after setup creates the vault and identity identifiers needed for those steps. -After Step 2 approval, setup configures: +Setup configures: - Organizational multitenancy and the Microsoft Entra ID allowed-tenants preview: home tenant, selected provider tenant, and preserved existing allowed provider tenants. - The app's enterprise application with assignment required, `Epp.Invoke`, and its assignment to - the created/reused Microsoft phone-provider service principal. -- That Microsoft principal's tenant-wide Graph `Application.Read.All`, the hostname-based - identifier URI, public JWE certificate, and Easy Auth restricted to the Microsoft caller. + the Microsoft phone-provider service principal. +- That Microsoft principal's tenant-wide Graph `Application.Read.All`, identifier URI, public JWE certificate, and Easy Auth caller restriction. - The disclosed outbound managed-identity federated credential for Soprano. ## Prerequisites for Step 2 @@ -117,10 +100,8 @@ After Step 2 approval, setup configures: - Azure CLI **2.60.0+ for FC1** or **2.48.1+ for EP1** on `PATH`, with GitHub, Azure, Microsoft Graph, Key Vault, and required SCM access. Setup can install Bicep after confirmation, but Azure CLI must already be installed. -- An Azure **user** account permitted to deploy at subscription scope, create the listed resources, - and create the scoped Azure role assignments. Bicep grants the operator **Key Vault Certificates - Officer** for issuance and **Key Vault Secrets Officer** for provider credentials, - scoped to this deployment's vault. +- An Azure **user** allowed to deploy at subscription scope and create scoped role assignments. + Bicep grants **Key Vault Certificates Officer** and **Key Vault Secrets Officer** on this vault. - **Certificate reuse:** the operator needs source-vault ARM read, certificate read, and secret-version metadata read. A different vault also requires permission to grant the Function secret read through RBAC or an access policy. The vault must be in the customer tenant; @@ -129,14 +110,12 @@ After Step 2 approval, setup configures: principal Graph `Application.Read.All`. Microsoft Graph operations use the current Azure CLI user session through `az ad` and `az rest`; Azure subscription roles alone do not grant these directory permissions. -- Microsoft Graph **beta** access for the Entra `signInAudienceRestrictions` allowed-tenants preview. - The selected provider tenant is allowed in addition to the app's home tenant, which Entra always allows. +- Microsoft Graph **beta** access for `signInAudienceRestrictions`. The provider tenant is added; + Microsoft Entra ID always allows the home tenant. See the [allowed-tenants schema](https://learn.microsoft.com/en-us/graph/api/resources/allowedtenantsaudience?view=graph-rest-beta). -- The selected **Linux FC1 or EP1** plan available in the region, with enough subscription - quota. Check service availability, provider registration, and quota separately. Resolve +- The selected **Linux FC1 or EP1** plan available in the region with enough quota. Resolve [`SubscriptionIsOverQuotaForSku`](Troubleshooting.md#deployment-fails-with-subscriptionisoverquotaforsku) - before retrying. After approval, setup registers required providers; the operator needs their - subscription-scoped `/register/action` permission. + before retrying. The operator needs subscription-scoped `/register/action`. - **.NET only:** install the .NET 8 SDK and allow NuGet access. Setup publishes for `linux-x64`, packages, and deploys automatically. - **Python:** Azure builds Linux dependencies; no local Python/pip install is required. The source @@ -144,20 +123,16 @@ After Step 2 approval, setup configures: ### Permissions are separate -For an Azure built-in-role example, **Owner at the selected subscription** includes deployment, -resource-provider registration, and role-assignment rights. Another reviewed option is +**Owner at the selected subscription** includes deployment, provider registration, and +role-assignment rights. Another option is **Contributor** plus **Role Based Access Control Administrator** at the required scope, subject to any role-assignment conditions. Setup deploys at subscription scope and creates a new resource -group, so access to an unrelated group does not cover the deployment. Request the approved scope -and duration, and activate eligible PIM roles before setup. See +group, so access to an unrelated group does not cover the deployment. Activate eligible PIM roles before setup. See [Azure roles](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles). Azure roles do not grant Entra/Graph permissions, and Entra roles do not grant Azure/vault access. -The Entra Privileged Role Administrator and directory permissions of the signed-in Azure CLI user -above are for setup. -The later Authentication Policy Administrator and policy permissions are a **separate activation** -step. Setup grants vault data roles to its Azure operator and Function identity; it does not -automatically grant them to every member of your operations team. +The signed-in user needs the listed Entra roles for setup. An Authentication Policy Administrator +handles activation separately. Setup grants vault roles only to its operator and Function identity. ### Install and check workstation tools @@ -173,8 +148,7 @@ az version dotnet --list-sdks ``` -Missing Bicep can be installed by interactive setup after confirmation. -These version checks do not establish Azure permissions, quota, feature availability, or provider access. +Interactive setup can install missing Bicep. | Choice | Azure runtime | Automatic deployment path | |---|---|---| @@ -182,20 +156,15 @@ These version checks do not establish Azure permissions, quota, feature availabi | .NET | .NET 8 isolated, Functions v4 | Verify source ZIP, publish for Linux with .NET 8, repackage and publish | | Python | Python 3.11, Functions v4 | Verify source ZIP; One Deploy with remote build on FC1, or validate/download and publish the SCM-built output on EP1 | -Setup verifies source hashes and records built-package hashes separately when bytes change. -FC1 Python builds inside Azure: its summary keeps the verified source hash and leaves -`packageSha256` null, not a guessed built hash. +Setup records source and built-package hashes separately. FC1 Python leaves `packageSha256` null +because Azure builds the package. -For [unattended execution](#noninteractive-example), preinstall prerequisites or explicitly allow -Bicep installation with `-InstallPrerequisites`. Azure CLI must already be installed. -The account picker reads enabled user subscriptions without changing the CLI default; select, -refresh, or use device-code sign-in. Graph reuses that directory through `az ad`/`az rest`. -Sign-in, MFA, consent, and Bicep-installation prompts are **not deployment approval**. +For [unattended execution](#noninteractive-example), preinstall prerequisites or allow Bicep +installation with `-InstallPrerequisites`. Azure CLI must already be installed. Sign-in, MFA, +consent, and installation prompts are separate from deployment approval. -Use `-ForceAuthentication` when the machine has ambiguous cached identities. It requires interactive -Azure CLI device-code authentication, does not clear shared token caches, and cannot be combined -with `-NonInteractive`. Azure RBAC uses the selected ARM token's validated `oid`; Graph user -readback comes from the same CLI directory session. +Use `-ForceAuthentication` for ambiguous cached identities. It requests device-code authentication, +does not clear shared caches, and cannot be combined with `-NonInteractive`. ## Step 2 - download and run one script diff --git a/setup/docs/Troubleshooting.md b/setup/docs/Troubleshooting.md index 25c663c..6774973 100644 --- a/setup/docs/Troubleshooting.md +++ b/setup/docs/Troubleshooting.md @@ -2,10 +2,8 @@ [Documentation](../../docs/README.md) | [Setup reference](README.md) | [Resume onboarding](../../docs/GETTING-STARTED.md) -Select the stage that failed and keep existing resources in place. Before rerunning setup, use -[safe recovery](#recover-without-destructive-cleanup). For a live-send timeout, check the provider -outcome before retrying. After deployment, complete provider acceptance and delivery checks -separately. +Select the failed stage and keep existing resources. Before rerunning, use +[safe recovery](#recover-without-destructive-cleanup). Check provider outcomes before retrying timeouts. ## Find your symptom @@ -22,10 +20,9 @@ separately. ## After-deployment triage -Keep the original error and timestamped summary. In a private support case, record tenant, -subscription, resource group, runtime/release, UTC test time, and safe correlation IDs. -Never attach complete settings, tokens, phone numbers, OTPs, private keys, encrypted bodies, or -raw provider responses. Keep body tracing off. +Keep the error and timestamped summary. For private support, record the tenant, subscription, +resource group, runtime/release, UTC time, and safe correlation IDs. Exclude settings, tokens, +phone numbers, OTPs, private keys, encrypted bodies, and raw responses. Keep body tracing off. | Symptom | First checks / owner | Safeguard | |---|---|---| @@ -40,18 +37,16 @@ raw provider responses. Keep body tracing off. | Requests exist but Python/.NET event query is empty | Use [runtime-specific discovery](../../docs/MONITORING.md#runtime-specific-log-discovery). Python extras/.NET scopes may not be exported; .NET host OpenTelemetry does not initialize direct worker export. | Do not apply JavaScript's JSON parser to other runtimes or promise complete dependencies/exceptions. | | No expiry email or alert | Operations owner checks vault certificate contacts/lifetime policy, independently configured reminders/action groups/rules, and notification tests. | Do not assume setup created alerts, or diagnostic settings deliver Key Vault expiry events. | -Keep nonce-match **booleans**, statuses, latency, safe IDs, and delivery-confirmation outcomes. -Evaluation success, provider acceptance, and recipient delivery are different evidence. -[Complete acceptance checks](../../docs/ONBOARDING.md#validate-the-deployed-endpoint) before activation. +Keep nonce-match **booleans**, statuses, latency, safe IDs, and delivery outcomes. Complete +[acceptance checks](../../docs/ONBOARDING.md#validate-the-deployed-endpoint) before activation. ## Recover without destructive cleanup -1. **Locate the failure.** Before approval, checks do not change Azure resources, although sign-in - or Bicep installation may occur. After approval, partial Azure or Microsoft Entra ID changes can - remain without a summary. Inspect the original error and **resource group > Deployments**. -2. **Fix, then rerun with the same reviewed inputs:** tenant, subscription, app, runtime, plan, - prefix, provider/route, and source/package versions. This keeps resource names, keys, and - endpoint configuration consistent. Active endpoints need a change window because setup has +1. **Locate the failure.** Before approval, only sign-in or Bicep installation can change the + environment. After approval, partial Azure or Microsoft Entra ID changes can remain. Inspect + the error and **resource group > Deployments**. +2. **Fix, then rerun with the same inputs:** tenant, subscription, app, runtime, plan, prefix, + provider/route, and source/package versions. Active endpoints need a change window; setup has no checkpoint resume or automatic undo. 3. **Verify the failed stage and complete acceptance checks.** Keep resource-group deletion, vault purge/recreation, encryption-credential removal, and shared-grant revocation out of @@ -74,20 +69,17 @@ Existing credentials require explicit **Reuse** or **Replace**, not an automatic | Credentials or the source vault's permission model changed after approval | Stop competing changes and rerun selection/approval against the current state. Do not force an outdated cleanup plan. | | An encryption credential has an unsupported type, or a non-encryption credential cannot be read | Have the application owner review the credential and read permissions. Setup cannot safely preserve or interpret it and will not blindly reset the credential collection. | -**Recover:** a failed key-reference/source-vault check leaves registered encryption credentials -intact, but resources/settings may have changed. If registration already succeeded, choose **Reuse** -with the printed certificate ID and the same prefix; another **Replace** requests another rotation. +**Recover:** a failed key-reference check leaves registered encryption credentials intact, but +resources or settings may have changed. If registration succeeded, choose **Reuse** with the +printed certificate ID and same prefix. -**Expected:** one approved application encryption credential. Cleanup removes only approved -`AsymmetricX509Cert` / `Encrypt` entries, not vault objects or unrelated credentials. -Coordinate maintenance and evaluation for every endpoint sharing the app; cached/in-flight -requests can still use a retired key. +**Expected:** one approved encryption credential. Cleanup removes only approved +`AsymmetricX509Cert` / `Encrypt` entries. Coordinate every endpoint sharing the app. ## Multi-region Front Door deployments -Build regional routing on top of a validated single-region endpoint by following the -[Front Door guide](../../docs/FRONTDOOR.md), including its readiness endpoint and origin checks. +Follow the [Front Door guide](../../docs/FRONTDOOR.md), including readiness and origin checks. - A 401/403/404 probe is unhealthy. Check the readiness path, origin host header, and profile-pinned network restriction. Never exempt SendOtp or disable Easy Auth. @@ -111,10 +103,9 @@ registration. A supported Linux EP1 region can still have zero quota for your su 5. Alternatively, choose another region only after checking its quota, service availability, and your residency and provider requirements. Review the updated deployment plan before approval. -**Expected:** the approved quota is visible and large enough for the deployment. A submitted -request alone is not enough. See [quota support](https://learn.microsoft.com/azure/quotas/quotas-overview). -Do not change SKU, authentication, or provider credentials to bypass the error; rerun with reviewed -inputs after quota is resolved, then validate the endpoint. +**Expected:** the approved quota is visible and sufficient. See +[quota support](https://learn.microsoft.com/azure/quotas/quotas-overview). Rerun with the reviewed +inputs after quota is available. ## Service plan selection or availability fails @@ -123,9 +114,8 @@ The parameter is required unattended. FC1 availability uses `az functionapp list EP1 uses the [ARM check below](#appservice-list-locations-rejects-ep1). Availability is plan-specific. FC1 has zero always-ready instances and a usage grant, not a zero-cost guarantee. -**Fix:** follow [plan selection and migration rules](README.md#service-plan-selection). -In-place FC1/EP1 switching is unsupported: use a different prefix, plus a dedicated app or an approved -key-continuity plan, and validate before policy changes. A new prefix does not authorize key rotation. +**Fix:** follow [plan selection and migration rules](README.md#service-plan-selection). Use a +different prefix and a dedicated app or approved key-continuity plan. A new prefix does not authorize key rotation. Existing EP1 deployments use `-ServicePlan EP1`; setup checks actual SKU when tags are absent. Never remove tags to bypass this guard. @@ -169,22 +159,16 @@ another Python runtime is not required to fix this check. ## A required Azure resource provider is not registered -The current setup detects missing providers such as `Microsoft.Web` during read-only preflight -and lists them in the resource plan instead of asking the customer to register them manually. -After `Yes` (or explicit noninteractive approval), it registers only the six namespaces needed by -this deployment in the supplied subscription. No registration occurs if approval is declined. +Setup lists missing providers such as `Microsoft.Web` during preflight. After approval, it +registers only the six required namespaces. Declining approval registers nothing. -Already registered providers are skipped. `Registering` is not a failure: Azure registers each -region separately, so setup proceeds when the needed region is exposed and retries recognized -registration-propagation errors. Metadata polling is limited to 60 checks with 10-second pauses; -regional propagation retries are limited to 12 attempts. An actively `Unregistering` provider is -not reversed automatically. +Registered providers are skipped. Setup proceeds from `Registering` when the region is exposed. +Polling is limited to 60 checks at 10-second intervals and 12 regional propagation retries. +It does not reverse `Unregistering`. -If registration fails, inspect the original Azure CLI error. The account needs subscription-scoped -resource-provider `/register/action` permission, generally included in Contributor or Owner. -Setup cannot grant this permission or bypass a subscription policy. It stops before creating the -certificate or deployment resources. Registrations already requested are left in place for a rerun; -the script does not unregister services that other workloads might now use. +If registration fails, inspect the Azure CLI error. The account needs subscription-scoped +`/register/action`, generally included in Contributor or Owner. Setup leaves completed registration +requests in place for the rerun. ## Azure CLI directory context does not match the selected tenant @@ -206,10 +190,8 @@ after account selection. It does not run `az logout`, `az account clear`, or del ## The expected cached account or subscription is missing -The account picker includes only enabled AzureCloud subscriptions associated with an Azure CLI -**user** login. Disabled subscriptions, service-principal sessions, tenant-level entries, and -other clouds are excluded. Refresh the CLI cache or choose device-code sign-in. A supplied -subscription ID is never replaced with another subscription. +The picker includes enabled AzureCloud subscriptions from an Azure CLI **user** login. Refresh +the cache or use device-code sign-in. Setup never replaces a supplied subscription ID. For unattended setup, establish the user session first and verify both the tenant and subscription: @@ -258,15 +240,11 @@ access to its SCM endpoint. Setup enables `SCM_DO_BUILD_DURING_DEPLOYMENT` and ` without `WEBSITE_RUN_FROM_PACKAGE` during the build, and requests Azure remote build explicitly. It never installs Windows Python dependencies for the Linux app. -SCM basic authentication remains disabled for both plans. The CLI uses Microsoft Entra authentication. -For EP1, the built -`site/wwwroot` snapshot must include the Python Functions dependency payload; an unbuilt source -archive is rejected even when an upload command returned success. The built output is then stored -in private Blob storage, and temporary remote-build settings are cleared. +SCM basic authentication remains disabled. For EP1, `site/wwwroot` must contain the Python +dependency payload; setup rejects unbuilt source, stores the built output in private Blob storage, +and clears temporary build settings. -If build, snapshot, publication, or startup fails after opening SCM ingress, setup attempts to -disable public ingress again. An inability to close ingress is an explicit error requiring -immediate administrator inspection. Do not bypass certificate errors or enable basic auth. +After build or startup failure, setup closes public ingress. If that fails, inspect it immediately. ## Azure CLI warnings break JSON parsing @@ -303,11 +281,9 @@ az rest --method get ` --output json ``` -A 403 is a directory authorization, Conditional Access, or role-activation problem; Azure -subscription Owner/Contributor does not fix it. Have the Entra administrator verify the user's -effective application-management permissions and activate Privileged Role Administrator when -the approved app-role grant requires it. Then rerun with the same reviewed inputs. Do not install -or connect the Graph PowerShell SDK as a workaround, and do not paste access tokens into tickets. +A 403 indicates directory authorization, Conditional Access, or role activation. Azure +Owner/Contributor does not grant this access. Verify application-management permissions and +Privileged Role Administrator activation, then rerun. Do not paste tokens into tickets. The Graph user ID is tracked for directory operations only. Azure role assignments continue to use the validated `oid` from the selected subscription's ARM access token.