From fc0047010615c2bad70110804b5cfcce32930394 Mon Sep 17 00:00:00 2001 From: Hou Chi Chan Date: Thu, 8 Oct 2026 11:24:38 -0700 Subject: [PATCH] docs: clarify customer onboarding, validation, and operations Document value sources and automatic settings, provider handoffs, assisted activation, runtime-aware monitoring, and safe lifecycle operations. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5f77103c-9908-482f-9d71-aee235428f9e --- README.md | 71 ++++- TECHNICAL.md | 44 ++- docs/APPLICATION-INSIGHTS.md | 7 + docs/CONTRACT.md | 121 +++++---- docs/MONITORING.md | 110 ++++++++ docs/ONBOARDING.md | 496 ++++++++++++++++++++-------------- dotnet/README.md | 22 +- javascript/README.md | 15 +- python/README.md | 15 +- setup/docs/README.md | 240 +++++++++++++--- setup/docs/Troubleshooting.md | 61 ++++- 11 files changed, 866 insertions(+), 336 deletions(-) diff --git a/README.md b/README.md index 581f8b6..3376fb0 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,13 @@ by SMS or voice. Start here to onboard **one deployment in one Azure region**. For implementation details, configuration, packaging, and security behavior, see the [technical reference](TECHNICAL.md). +**New customer path:** [confirm access and collect values](docs/ONBOARDING.md#before-purchasing-or-deploying) +→ [run guided setup](setup/docs/README.md#step-2---download-and-run-one-script) +→ [connect and validate](docs/ONBOARDING.md#complete-provider-authentication) +→ [activate policy](setup/docs/README.md#step-3---manually-validate-and-activate-policy) +→ [operate and monitor](docs/MONITORING.md). +You do not need to build locally, create `local.settings.json`, or read all three language guides. + ## Deployment options | Option | Onboarding | @@ -22,6 +29,12 @@ The guided setup deploys the Azure resources and configures the endpoint applica purchase a provider offer, grant access to a provider's API, or activate your authentication method policy. Those steps remain part of your onboarding. +**Before spending or activating:** confirm access to the provider's EPP integration and Microsoft's +approved tenant onboarding/test procedure. The sample does not establish eligibility, licensing, +or preview enrollment. It is not production certification: it has no durable queue, automatic +send retries, deduplication, whole-request deadline, or overlapping key rotation. Review the +[limitations](docs/CONTRACT.md#production-limitations) with your owners. + ## Single-region architecture ![Single-region External Phone Provider architecture](docs/images/single-region-architecture.png) @@ -54,7 +67,7 @@ Use a **dedicated nonproduction tenant and subscription** for your first deploym | Requirement | What to prepare | |---|---| -| Provider | An offer from a provider in **Security Store**, with the required SMS or voice route, account/sender registration, and provider onboarding completed. Confirm the provider is supported by the guided setup. | +| Provider | An offer from [Microsoft Security Store](https://securitystore.microsoft.com/private-solutions), with the required SMS or voice route, account/sender registration, and EPP account access. Guided setup supports Telesign and Soprano only. | | Workstation | Windows with PowerShell 7+ and Azure CLI 2.60.0+ for FC1 or 2.48.1+ for EP1 on `PATH`. Certificates are issued inside Key Vault, not the local certificate store. End-to-end setup from Linux or Azure Cloud Shell has not been validated. | | Network access | Access to GitHub, Azure, Microsoft Graph, and Key Vault. FC1 publication and EP1 Python builds also require access to the Function's SCM endpoint. | | Azure permissions | An Azure user account permitted to deploy at subscription scope, register required resource providers, and create scoped role assignments. | @@ -72,11 +85,17 @@ deploying. If Azure reports `SubscriptionIsOverQuotaForSku`, follow the [regional quota troubleshooting steps](setup/docs/Troubleshooting.md#deployment-fails-with-subscriptionisoverquotaforsku) before retrying. +The customer-designated **onboarding owner** coordinates the Azure operator, tenant/policy +administrator, provider administrator, and Microsoft support. This repository does not supply +a named contact. If the offer or required feature/test procedure is unavailable, follow the +[access gate](docs/ONBOARDING.md#before-purchasing-or-deploying), not a workaround that weakens authentication. + ## Onboard your endpoint ### 1. Set up your provider and application -In **Security Store > Provider offers**, purchase an offer and complete the provider's account, +In [Microsoft Security Store](https://securitystore.microsoft.com/private-solutions), review the +provider offer, then purchase and complete the provider's account, sender, and channel onboarding. Confirm that the provider supports the required SMS or voice route. Purchasing the offer does not deploy the Function. @@ -97,11 +116,17 @@ Have the following values ready before running setup: | Subscription ID | Selects the Azure subscription where resources will be deployed. | | Application client ID | Identifies the dedicated endpoint app you just registered. | | Azure region | Places this deployment in one region. | -| Channel and provider scope | Selects SMS or voice and the provider's Global or EU route. Provider scope is separate from the Azure region. | -| Language | Selects one of the equivalent Function implementations below. | +| Channel and provider scope | Selects one SMS or voice route and the provider's Global or EU label. This is separate from Azure region and is not a data-residency guarantee. | +| Language | Selects one Function implementation below. The HTTP contract is shared; caching and telemetry differ by runtime. | | Service plan | Selects Flex Consumption FC1 or Premium EP1. Required explicitly for unattended setup. | | Resource prefix | Use 2-8 lowercase letters or digits, starting with a letter, such as `contoso`. Setup adds resource-specific names and a suffix. | +Use the [central values table](docs/ONBOARDING.md#values-and-ownership) for exact portal locations, +parameter names, client-ID versus Object-ID distinctions, and settings setup creates automatically. +For a second independent channel/provider endpoint, use a new prefix and dedicated app. +Changing channel/provider/region with the same subscription/app/prefix is a reconfiguration, +not an additional deployment; see [deployment separation](docs/ONBOARDING.md#inputs-you-supply-to-setup). + ### 2. Deploy the endpoint No repository clone is needed. Download [Setup-Epp.ps1](setup/Setup-Epp.ps1), inspect it, then run it @@ -149,6 +174,9 @@ consent, and prerequisite-installation prompts are separate from deployment appr #### What successful setup produces +**Setup has already deployed the code and Azure settings.** Skip local configuration and manual +ZIP publication unless you are developing a custom implementation. + - A dedicated resource group, selected Linux FC1 or EP1 plan, Function App, and storage account. - Key Vault and the encryption certificate/key configuration. - Managed identities, scoped role assignments, and Easy Auth caller restrictions. @@ -161,7 +189,8 @@ an authentication error**; it is the endpoint's caller-authentication gate. Save the public certificate and timestamped deployment summary from `epp-output` beside the downloaded script, or your selected output directory. Confirm the tenant, application client ID, endpoint URL, and encryption key ID with your EPP onboarding owner. Private keys are not included -in the summary. +in the summary. Use the [summary field guide](setup/docs/README.md#read-the-deployment-summary) +to locate the vault, Function, telemetry resources, and version information. The encryption certificate is issued inside Key Vault; setup downloads only the public certificate. The Function is pinned to a specific private-key secret version. Certificate renewal and the @@ -180,15 +209,19 @@ action depends on the authentication method supported by its integration: | Authentication method | Required action | |---|---| -| API key or token | Store the required credentials and any matching account/customer identifier in Key Vault using the integration's documented secret names. Confirm the Function identity has Key Vault Secrets User access. | -| OAuth with managed identity | Complete the provider's consent/application-role onboarding for the existing multitenant application. Setup configures the supported outbound managed-identity federation, but does not grant access to the provider API. | +| Telesign API key | Enter `telesign-api-key` and `telesign-customer-id` in the setup-created vault using the [safe portal steps](docs/ONBOARDING.md#telesign-enter-and-verify-the-two-vault-secrets). Setup already grants the Function system identity Key Vault Secrets User. | +| Soprano OAuth | Complete the [provider-admin handoff](docs/ONBOARDING.md#soprano-provider-administrator-handoff) for the existing multitenant application. Setup configures the outbound federation, but does not grant access in the provider tenant. | Replace any test provider values before live validation. Never put API keys in source code or local settings. See [provider authentication](docs/ONBOARDING.md#provider-credential-names) for details. #### Validate before activation -Use the supported EPP test procedure with your onboarding owner. Validate the deployed endpoint, +Arrange Microsoft's approved EPP test procedure with your onboarding owner. This repository has +offline tests, **not a self-service authorized caller/token tool**. A normal customer CLI token +cannot impersonate the allowlisted Microsoft caller. See the +[test handoff, negative check, and evidence checklist](docs/ONBOARDING.md#validate-the-deployed-endpoint). +If the authorized procedure is unavailable, stop before activation. Validate the deployed endpoint, not just a locally running Function. | Check | Expected result | @@ -198,9 +231,10 @@ not just a locally running Function. | Controlled live test | The provider accepts the selected SMS or voice request and the test recipient receives the message or call. Provider acceptance alone is not proof of delivery. | | Operational visibility | Review Application Insights for the request outcome without recording phone numbers, message bodies, tokens, private keys, or nonce values in shared logs. | -Configured workers can acquire and refresh provider credentials in the background without sending -an OTP, even while evaluation requests run. Check for `credential_refresh_failed` warnings before -live testing; an evaluation success does not validate provider credentials. +Configured workers can acquire credentials at startup without sending an OTP; JavaScript/Python +also poll for refresh, whereas .NET retrieves replacements on cache misses. Check collected +`credential_refresh_failed` warnings before live testing; an evaluation success or absence of +warnings does not validate provider credentials. Stop and resolve failed checks before changing the authentication policy. A successful package deployment is not evidence that provider credentials, caller authentication, or handset delivery work. @@ -218,6 +252,8 @@ method policy using the endpoint URL and application client ID from the deployme **Setup does not activate policy.** If the supported policy fields are unavailable, stop and obtain the supported procedure from Microsoft rather than guessing an update. Policy backup and rollback remain administrator-owned; deleting Azure resources does not roll back policy. +Public Graph SMS/voice resource documentation does not document the EPP `url`/`appId` fields; +this is an assisted product-specific step, not a public PATCH example. Follow the [validation and activation procedure](setup/docs/README.md#step-3---manually-validate-and-activate-policy) before using the endpoint. @@ -229,7 +265,13 @@ before using the endpoint. - [ ] Unauthorized callers are rejected and authorized evaluation succeeds without delivery. - [ ] A controlled live test confirms both provider acceptance and recipient delivery. - [ ] An administrator has backed up, activated, and read back the selected authentication policy. -- [ ] The owner has retained the deployment summary and documented the manual rollback procedure. +- [ ] Request/log ingestion is verified for the chosen runtime, and alert notifications are tested. +- [ ] Credential and certificate-renewal owners, expiry reminders, and retention/cost controls are assigned. +- [ ] The owner has retained the deployment summary and documented [rollback and teardown](setup/docs/README.md#rollback-and-decommissioning). + +Setup creates Application Insights and a workspace, **not alerts, action groups, availability +tests, or certificate contacts**. Complete the [monitoring setup](docs/MONITORING.md#5-set-up-notifications-and-alert-rules) +and [certificate lifecycle](setup/docs/README.md#encryption-certificate-lifecycle) steps manually. For setup failures, start with the [troubleshooting guide](setup/docs/Troubleshooting.md). For application behavior or configuration details, use the technical documentation below. @@ -241,5 +283,6 @@ application behavior or configuration details, use the technical documentation b - [Optional manual Azure Front Door onboarding](docs/FRONTDOOR.md) - regional setup, readiness, security, and test results. No deployment script is provided. - [Setup guide](setup/docs/README.md) - permissions, deployment prompts, validation, and manual rollback. - [Technical reference](TECHNICAL.md) - configuration, packages, provider behavior, and security. -- [Detailed configuration and validation](docs/ONBOARDING.md) - local development and deployment checks. -- Implementation guides: [JavaScript](javascript/README.md), [.NET](dotnet/README.md), [Python](python/README.md). +- [Customer configuration and validation](docs/ONBOARDING.md) - value sources, automatic settings, provider handoffs, acceptance checks, and optional developer work. +- [HTTP and provider contract](docs/CONTRACT.md) - implementation behavior and production limits. +- Optional developer guides (not additional customer deployment steps): [JavaScript](javascript/README.md), [.NET](dotnet/README.md), [Python](python/README.md). diff --git a/TECHNICAL.md b/TECHNICAL.md index 0a547f8..083023e 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -21,9 +21,10 @@ secrets in Key Vault. | Python (v2 model) | Available | [python/](python/) | All implementations conform to the **language-agnostic contract** in -[docs/CONTRACT.md](docs/CONTRACT.md): identical HTTP API, provider-adapter shape, config/env var -names, Key Vault secret names, and behaviors (fail-closed, managed identity, privacy). Pick any folder -and follow its README. +[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 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 @@ -47,6 +48,11 @@ Certificate renewal and policy activation remain manual. The home tenant remains ## 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), +not deploy a second ZIP. + Download the latest successful CI ZIP for your chosen language: | Language | Download | Contents | @@ -126,7 +132,7 @@ extend it deliberately if you add runtime assets, and never put secrets in appli ## The design in one line SAS → Easy Auth → anonymous HTTP handler (`POST /api/SendOtp`, validate envelope + decrypt JWE) → -configured provider (API key) → HTTP result with nonce on success. +configured provider (API key or OAuth) → HTTP result with nonce on success. Only provider acceptance returns the nonce for live requests. Incoming `mode: 2` (evaluation) is the generic shutter: after platform authentication, validate and decrypt, then echo the nonce without calling a provider. @@ -139,6 +145,10 @@ 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. + 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. @@ -157,7 +167,7 @@ how code accesses configuration, not the environment-variable names. | Variable | When needed | Value | |---|---|---| | `AzureWebJobsStorage` | Functions host storage | Local sample: `UseDevelopmentStorage=true` with Azurite running. Configure Azure host storage separately for the selected plan. | -| `FUNCTIONS_WORKER_RUNTIME` | Functions host | `node`, `python`, or `dotnet-isolated`. Choose the value matching your implementation. | +| `FUNCTIONS_WORKER_RUNTIME` | Local host / EP1 | `node`, `python`, or `dotnet-isolated`. FC1 uses `functionAppConfig.runtime` instead; do not add this EP1 setting to FC1. | | `EPP_DECRYPTION_KEY_PEM` | Every request | Local test PEM or base64 PEM. In Azure, use a Key Vault reference resolving to the private-key secret. Guided setup pins the PEM backing secret of its Key Vault certificate. | | `EPP_ENCRYPTION_KEY_ID` | Optional | Expected encryption key ID; mismatch only produces an advisory warning. | | `EPP_PROVIDER_NAME` | Live delivery | Selected adapter's manifest ID. No default provider. | @@ -188,12 +198,13 @@ 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. -Configured providers are [prepared automatically per worker](docs/CONTRACT.md#credential-caching-and-refresh): -Telesign's Key Vault credentials, Soprano's managed-identity assertion, and its final Entra access -token are cached and refreshed before expiry. Refresh never sends an OTP. Evaluation handling still -skips provider work, but a worker with a configured provider can independently acquire credentials -at startup or during background refresh. Leave `EPP_PROVIDER_NAME` unset for local evaluation-only -work without credential acquisition. No extra refresh app settings are required. +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. Leave `EPP_PROVIDER_NAME` unset for local evaluation-only work without credential acquisition. +The setup-written cache switches do not change current checked-in runtime behavior; verify your +selected package rather than assuming plan selection enables/disables caching. 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//)` @@ -277,16 +288,19 @@ authentication; [separate deployed security checks](docs/ONBOARDING.md#4-package ## Docs - **[README.md](README.md)**: single-region customer onboarding. -- **[docs/ONBOARDING.md](docs/ONBOARDING.md)**: detailed configuration, security, deployment, and validation. +- **[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 events, per-request summaries, +- **[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. ## Contributing a language or provider -- **New provider** (in any language): add one adapter file exposing `manifest` + `buildRequest` + - `parseResponse`; no engine changes. See the language folder's README. +- **New provider**: follow the selected language's provider base/interface and registration steps. + Internal method names differ by runtime. A bundled adapter is not automatically a guided setup + profile or an approved provider offer. - **New language**: mirror the folder structure, implement the contract, add the same test scenarios, and wire it into [.github/workflows/ci.yml](.github/workflows/ci.yml). diff --git a/docs/APPLICATION-INSIGHTS.md b/docs/APPLICATION-INSIGHTS.md index aae8b70..56dbff2 100644 --- a/docs/APPLICATION-INSIGHTS.md +++ b/docs/APPLICATION-INSIGHTS.md @@ -11,6 +11,13 @@ deploy resources, enable additional instrumentation, or configure alert rules, a or availability tests. Use the [setup guide](../setup/docs/README.md) for deployment. Azure Front Door is optional; the Function telemetry described here also applies without it. +**First-time operator:** locate `resources.applicationInsights` and `resources.logAnalytics` in +the [saved deployment summary](../setup/docs/README.md#read-the-deployment-summary). Verify collection +here, then follow the [runtime-specific queries](MONITORING.md#runtime-specific-log-discovery) +and [alert setup](MONITORING.md#5-set-up-notifications-and-alert-rules). Authorized evaluation/live +testing must use the [customer test handoff](ONBOARDING.md#validate-the-deployed-endpoint); +deployment does not provide a customer token for the Microsoft caller. + ## How telemetry reaches Application Insights There are two execution processes to consider: the **Functions host**, which dispatches diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md index 2ed1155..939f6d1 100644 --- a/docs/CONTRACT.md +++ b/docs/CONTRACT.md @@ -126,16 +126,18 @@ Tokens are treated as opaque: the Function checks SDK expiry metadata, not custo Soprano remains responsible for signature, issuer, audience, expiry, permissions, and account validation. Credential instances and their SDK caches are reused for the configured tenant/application/identity. -One [worker-local refresh loop](#credential-caching-and-refresh) warms both exchange stages; a -snapshot of the latest provider token keeps refresh off the delivery path. JavaScript and .NET -bound the shared acquisition to 2.5 seconds, independent of individual waiters. JavaScript links +JavaScript/Python use a [worker-local refresh loop](#credential-caching-and-refresh) for both +exchange stages. .NET warms at startup and fetches a replacement on a cache miss; it has no poller. +JavaScript bounds shared acquisition to 2.5 seconds independently of individual waiters; +.NET bounds each fetch to 2.5 seconds with cancellation linked to that caller. JavaScript links cancellation through an SDK HTTP-client wrapper because `getToken` options alone are insufficient in the installed SDK. Python bounds caller waits and SDK connect/read inactivity to 2.5 seconds; shared synchronous retrieval may finish after a waiter leaves. It uses `get_token_info` for refresh hints when supported, otherwise `get_token`; a failed acquisition never falls back to another API. -Credential SDK transport retries are disabled; failed refreshes use the fixed polling cadence described -below. These are not end-to-end delivery deadlines. JavaScript suppresses SDK logs in the +Credential SDK transport retries are disabled. JavaScript/Python failed refreshes use the polling +cadence below; .NET retries credential acquisition on a later cache miss. These are not end-to-end +delivery deadlines. JavaScript suppresses SDK logs in the acquisition's asynchronous context. Python filters Azure Identity/Core/MSAL records on configured handlers in that context; configure logging sinks before handling requests. .NET disables credential diagnostics. Keep platform body tracing off and never log credential objects or tokens. @@ -260,7 +262,7 @@ specifications; never include secret values in documentation or the settings sam ### Telesign EPP integration SMS and Voice use the complete provider-approved URLs selected from the provider profile. The provider -supplies `recipient.phone_number`, the unchanged `message.text`, optional `message.language`, one +supplies `recipient.phone_number`, `message.text` (unchanged for SMS, paced/repeated for voice), optional `message.language`, one selected `channels[].channel`, and `correlation_id`. Keep the leading `+` in the E.164 phone number. Phase 1 supports Basic and Digest; this sample implements Basic only. Per @@ -338,46 +340,41 @@ subscription activation and changing tenant policy belong to provisioning, not t ### Credential caching and refresh -Provider credential management is automatic for configured providers in every runtime. It changes -when credentials are fetched, not the HTTP/nonce contract, caller authentication, FIC, provider -selection or provider request format. The decryption-key Key Vault reference remains separate and -is still resolved by the platform; this cache does not rotate or replace JWE keys. +Provider credentials are process-local, distinct from the platform-resolved decryption-key +reference. Credential acquisition never sends an OTP or changes caller authentication. +Restart workers after configuration changes. The setup-written +`EPP_KEY_VAULT_CACHE_ENABLED` / `EPP_ACCESS_TOKEN_CACHE_ENABLED` switches are not read by the +current checked-in implementations; verify the selected release before relying on plan-specific +cache control. -The selected provider's credential specification determines which concrete cache is created: - -| Authentication mode | Cache | Acquisition | +| Runtime | Startup and replacement behavior | Operator consequence | |---|---|---| -| `apiKey` | `ApiKeyCache` | Fetch the provider's Key Vault secrets using managed identity. Cache the complete key/customer-ID bundle in .NET `MemoryCache`, JavaScript `lru-cache`, or Python `cachetools.TTLCache`. | -| `oauth` | `AccessTokenCache` | Reuse Azure Identity's managed-identity and client-assertion credentials and their SDK caches. Retain only the latest usable provider token. No Key Vault access. | - -Only the selected cache starts. Its credential configuration is bound on first use; app-setting -changes require a worker restart, not live cache switching. API-key bundles are published only -after all required reads succeed. Refresh targets four minutes after retrieval and hard expiry -is five minutes; reads or failed refreshes never extend the lifetime. Provider tokens retain their -original SDK expiry and are unusable with 30 seconds or less remaining. Tokens are never persisted. - -The shared coordinator prepares credentials at startup and polls the selected cache every 30 seconds. -`ApiKeyCache` skips retrieval until its refresh target is due; `AccessTokenCache` consults both SDK -credentials and lets the SDK decide whether network acquisition is needed. There is no separate MI -cache, adaptive expiry timer, or exponential retry policy. Failures retry on a later poll; requests -cannot start another acquisition within the same 30-second window. This is best-effort scheduling, -not an exact refresh deadline. Timing policy uses named constants, not extra app settings. - -Concurrent cold requests share one acquisition. Requests with usable cached credentials do not -wait for background refresh. After hard expiry, they join the shared acquisition or fail closed. -JavaScript's app-start hook, Python's initialization thread and .NET's hosted service start only -credential preparation, never provider delivery. Shutdown stops polling and prevents late -publication; `close()`/`Dispose()` is terminal. JavaScript and .NET propagate the 2.5-second -acquisition deadline to SDK HTTP. Python bounds caller waits and SDK connect/read inactivity to -2.5 seconds but cannot forcibly cancel synchronous I/O; daemon secret reads stay shared until -both finish and cannot block process exit. Missing/broken provider configuration does not prevent -evaluation. - -**Prewarming does not guarantee the first request meets the caller's timeout.** Worker readiness, -scale-out and ingress overhead still matter. Refresh does not retry or deduplicate provider sends. -Background failures log only `credential_refresh_failed`, `cacheKind` and the fixed -`credential_unavailable` reason, without request IDs, credential values or SDK exception details. -Per-request `providerCredentialElapsedMs` continues to measure the caller's resolution time. +| [JavaScript](../javascript/src/functions/credentials.js) | Selects `ApiKeyCache` (`lru-cache`) or `AccessTokenCache`; warms at startup and polls every 30 seconds. API-key bundles target refresh at four minutes and expire at five. OAuth reuses SDK credentials/caches and the latest usable token. | Warm reads do not wait for refresh. Concurrent cold callers share acquisition; failures are retried at the polling cadence, not by sending another OTP. | +| [Python](../python/src/credentials.py) | Equivalent selected-cache/polling policy using `cachetools.TTLCache` and a daemon initialization/refresh thread. | Callers can stop waiting while synchronous acquisition remains shared. Connect/read inactivity timeouts are not a hard total I/O deadline. | +| [.NET](../dotnet/Src/CredentialTokenService.cs) | Hosted service warms the selected provider once. `MemoryCache` holds provider-supplied credentials until absolute expiry; the next miss fetches again. No periodic refresh loop. API-key credentials expire after five minutes; Soprano entries expire 30 seconds before SDK token expiry. | The first request after expiry can pay acquisition latency. `GetOrCreateAsync` does not guarantee single-flight retrieval for concurrent misses. A quiet app need not emit periodic credential events. | + +JavaScript/Python retry failed refresh on a later poll without extending cached credential +lifetime; requests with expired credentials join acquisition or fail closed. SDK token expiry +is preserved, and tokens with 30 seconds or less remaining are unusable. These implementations +stop polling/prevent late publication at shutdown. .NET has no poller to stop: failed cache +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. + +All runtimes fetch a complete API-key/customer-ID bundle before caching it and use managed identity +for vault access. Soprano reuses the managed-identity and client-assertion SDK credential instances +without Key Vault or a client-secret fallback. Evaluation skips credential resolution on the +request path even when independent startup/refresh work runs. + +**Prewarming is not traffic readiness or a latency guarantee.** Test cold, warm, expired, rotated, +and concurrent cases for the deployed runtime. The `credential_refresh_failed` event name is also +used by .NET for startup/cache-fill failures; it does not imply a periodic job. Field casing and +collection differ by runtime; use [runtime-specific discovery](MONITORING.md#runtime-specific-log-discovery). +Do not log SDK exception details or tokens. No cache retries/deduplicates provider sends. --- @@ -494,7 +491,8 @@ A parsed provider rejection uses `provider_response_processed` with its non-succ fixed failure reason. In JavaScript and Python, every service event carries the Function request/invocation IDs, the Microsoft trace IDs available -at that point, and the known channel, evaluation flag and selected provider. The initial event can +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 @@ -510,7 +508,7 @@ from header to envelope. The identifiers are intentionally not named simply `req | `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 the endpoint application's inbound audience or a Microsoft request ID. | +| `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. | @@ -538,8 +536,17 @@ advisory warnings use Warning; 5xx failures and timeouts use Error. Keep applica 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 summary. Configure collection and retention deliberately without enabling SDK/body tracing. -Easy Auth rejections occur before the handler and appear in platform telemetry, not these events. +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. --- @@ -554,9 +561,9 @@ Each language keeps lightweight offline tests covering representative applicatio - Awaited delivery, nonce acknowledgement and privacy-safe logging, including the shared service-event order and safe field cases in [contract.json](../tests/fixtures/contract.json), identifier provenance, error paths, provider-body timeouts and concurrent request isolation. -- Selected-cache-only startup, shared credential retrieval, fixed refresh/retry cadence, hard expiry, - token lifetime preservation and shutdown using controlled clocks and - fake dependencies. JavaScript tests also exercise cancellation through the actual SDK pipeline. +- Runtime-specific credential caching using fake dependencies: JavaScript/Python cover selected-cache + startup, shared retrieval, polling, expiry, and shutdown; .NET covers its startup/cache-miss model. + JavaScript tests also exercise cancellation through the actual SDK pipeline. The sample deliberately omits exhaustive input permutations and SDK internals. These tests use local keys and mocked external services; they do not send SMS and **do not test Easy Auth or platform @@ -572,16 +579,18 @@ 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 Preview 1 setup guide requires asynchronous delivery after acceptance. This sample still waits - for the provider and has no durable queue or automatic retry implementation; it does not satisfy that - timing architecture merely because the setup script deploys it. +- 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 text is forwarded unchanged. Digit-by-digit rendering required by the setup guide must be - verified for the chosen voice integration; unspaced numeric text is not guaranteed to be spoken correctly. +- 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. diff --git a/docs/MONITORING.md b/docs/MONITORING.md index de5330a..cb18918 100644 --- a/docs/MONITORING.md +++ b/docs/MONITORING.md @@ -9,6 +9,11 @@ permissions, privacy, charges, and alert routing before creating anything. All thresholds and intervals below are **starting points, not SLAs**. This guide does not change deployed diagnostics, sampling, retention, or alert settings. +For the first deployment, finish the [customer acceptance checks](ONBOARDING.md#validate-the-deployed-endpoint) +with the authorized test operator. Read [Application Insights](APPLICATION-INSIGHTS.md) for +collection/identity details, then use this guide to establish queries and alerts. You do not +need Front Door, a Workbook, or a new exporter merely to locate your existing single-region logs. + ## 1. Record the deployment and confirm telemetry Keep a private inventory with one row per expected regional endpoint: @@ -284,6 +289,86 @@ workspace branches as in the request query, add a region label to each branch, and include it in the final grouping. Do not combine copies of the same event exported through multiple pipelines. +### Runtime-specific log discovery + +Do not search all languages for `logType: "request"` or parse every trace as JavaScript JSON. +Current runtimes emit fixed completion events, with different formats: + +| Runtime | Emission in source | Fields to verify in the deployed trace | +|---|---|---| +| JavaScript | JSON string from [logging.js](../javascript/src/functions/logging.js): `logType: "service"`, `eventName: "request_completed"`. | camelCase `functionRequestId`, `httpStatus`, `result`; parse `Message` using the JavaScript queries above. | +| Python | [otp_log.py](../python/src/otp_log.py): fixed message `OTP request completed`; logging extras include `event_name`, `event_id`, `httpStatus`, `result`, `functionRequestId`. | Whether extras survive in `Properties`, and their actual names. A message alone does not retain its status/result. | +| .NET | [OtpLog.cs](../dotnet/Src/OtpLog.cs): typed `ILogger` event `request_completed` (1401), message beginning `Request completed: HTTP`. | `HttpStatus`, `Result`, `ElapsedMs` and `FunctionRequestId` scope, if preserved by the exporter. Event ID/name may be separately exported or absent. | + +First discover property **names**, without dumping message bodies or values: + +```kusto +AppTraces +| where TimeGenerated >= ago(1h) +| where _ResourceId =~ "" + and AppRoleName == "" +| mv-expand PropertyName = bag_keys(Properties) +| summarize StoredRows = count() by PropertyName = tostring(PropertyName) +| order by StoredRows desc +``` + +No property rows does not prove no traces: the exporter may have sent only formatted messages. +Use the [coverage query](APPLICATION-INSIGHTS.md#read-only-queries-to-establish-what-is-present) +to distinguish absent properties from absent logs. Inspect a controlled nonproduction invocation +privately; exporters can prefix or rename structured properties. + +**Python example:** use this only if `event_name`, `result`, and `httpStatus` are preserved as +top-level `Properties` keys. The fixed-message branch makes missing completion properties +visible instead of silently dropping those rows: + +```kusto +AppTraces +| where TimeGenerated >= ago(1h) +| where _ResourceId =~ "" + and AppRoleName == "" +| where tostring(Properties.event_name) == "request_completed" + or Message == "OTP request completed" +| extend Result = tostring(Properties.result), HttpStatus = toint(Properties.httpStatus) +| extend SchemaStatus = iff(isempty(Result) or isnull(HttpStatus), + "missing completion properties", "properties present") +| summarize StoredRows = count() by SchemaStatus, Result, HttpStatus +``` + +**.NET example:** use the emitted completion-message prefix to find candidate rows, then verify +the structured keys. If the exporter prefixes keys, change the property accessors accordingly; +do not assume missing status equals zero or success. + +```kusto +AppTraces +| where TimeGenerated >= ago(1h) +| where _ResourceId =~ "" + and AppRoleName == "" +| where Message startswith "Request completed: HTTP" +| extend Result = tostring(Properties.Result), HttpStatus = toint(Properties.HttpStatus) +| extend SchemaStatus = iff(isempty(Result) or isnull(HttpStatus), + "missing completion properties", "properties present") +| 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. + +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. + +`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 +periodic poller. See [credential behavior](CONTRACT.md#credential-caching-and-refresh). +Absence of a warning, especially in an idle app or a missing export pipeline, is not a credential +health check. + ## 5. Set up notifications and alert rules Have an operator review and create these resources in a nonproduction environment @@ -326,6 +411,31 @@ 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 + +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. +Configure the vault-wide certificate contacts and retain the certificate's `EmailContacts` +lifetime action, following 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 +same-key renewal in nonproduction. If your operations process uses Key Vault near-expiry/new-version +events, an operator must 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. + +For provider API keys, track the provider's expiration/revocation policy and verify a coordinated +matching-key/customer-ID transition. For Soprano, monitor failed credential acquisition and provider +authorization failures; do not treat short-lived access-token renewal as certificate renewal. +Evaluation is insufficient for either provider's delivery readiness. + +Review telemetry volume, effective retention, and daily caps; caps can create blind spots, not just +reduce cost. Configure Azure Cost Management budgets/notifications for the chosen scope and review +provider billing separately. Budgets do not stop spending. Assign operators to act on alerts and +periodically verify routing, firing, and resolution rather than only creating rules once. + ### Log search alerts In **Azure Monitor > Alerts > Create > Alert rule**, select the workspace and diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index 8e688fb..ee052df 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -1,201 +1,295 @@ -# EPP Detailed Configuration and Validation - -For first-time single-region deployment, start with the [main onboarding guide](../README.md). -This guide covers detailed configuration, local development, and validation of the External Phone -Provider (EPP) Function. Choose one language: -[JavaScript](../javascript/README.md), [Python](../python/README.md), or [.NET](../dotnet/README.md). -Use [CONTRACT.md](CONTRACT.md) for the full request contract and production limitations. - -For a single public URL backed by multiple regions, see the -[manual Front Door guide](FRONTDOOR.md). You'll need to implement readiness checks, keep encryption -keys consistent across regions, restrict origin access, and test the deployment. This onboarding -flow doesn't automate those steps. - -1. **Purchase a provider offer from Security Store.** - - Open **Security Store > Provider offers**, purchase an offer, and activate the provider account. - Confirm EPP endpoint access and support for your required channels. Obtain the provider's API key - and matching customer/API ID, if required. - Purchasing an offer does not deploy this Function or install a missing provider adapter. - -2. **Run the app setup script.** - - - Use the [guided EPP setup](../setup/docs/README.md) after manually creating only the dedicated - endpoint application registration. Download only `setup/Setup-Epp.ps1`; it retrieves commit-pinned support scripts, - Bicep, provider profiles, and the selected language package. Choose a provider, SMS or voice, - Global or EU, a service plan, and a resource prefix, then approve one complete deployment plan. - See [service plan selection](../setup/docs/README.md#service-plan-selection) for plan defaults, - billing, migration limits, and the runtime package needed for cache control. - - After approval, the script configures the app registration and enterprise application, creates - the Microsoft phone-provider service principal, assigns `Epp.Invoke`, grants it Microsoft Graph - `Application.Read.All`, and restricts the multi-tenant app through the Entra allowed-tenants - preview to its home tenant plus the selected provider tenant. It then deploys the Function and - configures Easy Auth. It does not purchase the provider offer, grant provider API consent/roles, - or activate the EPP policy. - The encryption certificate is issued inside Key Vault, not on the setup workstation. Setup - registers its public certificate in Entra and pins the Function to its PEM backing secret version. - Renewal remains manual; see the [certificate lifecycle](../setup/docs/README.md#encryption-certificate-lifecycle) - for same-key renewal and the separate Entra update. - -3. **Complete provider authentication and settings.** - - - The setup script writes the selected provider route and authentication settings: - - | Provider | API credential secret | Matching identity secret | Authentication | - |---|---|---|---| - | `telesign` | `telesign-api-key` | `telesign-customer-id` | Basic: base64 of `customer-id:api-key` | - | `soprano` | None | None | OAuth client assertion using the outbound user-assigned managed identity | - | `infobip` | `infobip-api-key` | None | `Authorization: App ` | - | `sinch` | `sinch-api-token` | None | Static token authentication | - - For Telesign, store the raw key and customer ID separately and grant the Function identity - **Key Vault Secrets User** access. For Soprano, complete provider consent/application-role - onboarding for the existing multitenant application. The setup creates the disclosed federated - identity credential; it does not grant access to Soprano's API. - - - Start with [local.settings.sample.json](local.settings.sample.json) beside the chosen app's - `host.json`. Replace placeholders in `Values`; all values must be strings. `EPP_PROVIDER_ENDPOINT` - is the complete provider-approved request URL selected for the channel and Global/EU region. - `EPP_PROVIDER_AUTH_MODE` must match the adapter: `apiKey` for Telesign or `oauth` for Soprano. - Provider API keys stay in Key Vault, not `Values`. - - Reuse the values from the setup-created Function App's environment variables; do not create - a second set of application or identity IDs for local settings. The shared sample uses these - same names for JavaScript, Python, and .NET: - - | Local `Values` keys | Value to reuse from setup | - |---|---| - | `EPP_PROVIDER_NAME`, `EPP_PROVIDER_AUTH_MODE` | Selected provider and its authentication mode. | - | `EPP_PROVIDER_ENDPOINT`, `EPP_PROVIDER_CHANNEL`, `EPP_PROVIDER_ENDPOINT_REGION` | Complete selected send URL, `sms` or `voice`, and `global` or `eu`. Do not append an API path. | - | `EPP_PROVIDER_TENANT_ID` | Provider tenant, not the customer's home tenant. | - | `EPP_PROVIDER_APP_ID`, `EPP_PROVIDER_SCOPE` | Soprano API application ID and the exact selected scope, including `/.default`. These are not the calling application's ID. Leave blank for API-key providers. | - | `EPP_OUTBOUND_CLIENT_ID` | Existing calling application's Application (client) ID used during setup, not its Object ID or Soprano's API ID. | - | `EPP_OUTBOUND_MI_CLIENT_ID` | Setup-created outbound user-assigned identity's Client ID, not its principal/Object ID. | - | `EPP_PROVIDER_TIMEOUT_MS`, `EPP_PROVIDER_RETRY_INTERVAL_MS` | Selected profile values, as strings. The retry interval does not enable runtime retries or shutter mode. | - | `KEY_VAULT_URL` | Setup-created or explicitly selected credential vault URL, not a secret value. | - - The outbound IDs are used only for Soprano OAuth; leave them blank for local API-key-only - configurations. `EPP_PROVIDER_APP_ID` and `EPP_PROVIDER_ENDPOINT_REGION` are setup metadata; - the runtime uses the selected scope and full URL directly. Keep Azure host storage, package - URLs, Application Insights, and Easy Auth configuration in Azure rather than copying the - complete cloud environment into a local settings file. Use the local storage and key guidance below. - - **Migrating local settings from the earlier optional-JWT branch:** replace - `EPP_PROVIDER_APPLICATION_ID` with `EPP_OUTBOUND_CLIENT_ID` and `EPP_PROVIDER_MI_CLIENT_ID` - with `EPP_OUTBOUND_MI_CLIENT_ID`. Reuse `EPP_PROVIDER_TENANT_ID` and the setup-selected - `EPP_PROVIDER_SCOPE`. Remove `EPP_PROVIDER_JWT_ENABLED`; current `main` uses - `EPP_PROVIDER_NAME=soprano` with `EPP_PROVIDER_AUTH_MODE=oauth`, not an optional JWT flag. - Replace the old base endpoint with the complete send URL. `AZURE_CLIENT_ID`, if separately - configured for Key Vault, is not a substitute for `EPP_OUTBOUND_MI_CLIENT_ID`. - - Set `FUNCTIONS_WORKER_RUNTIME` to `node`, `python`, or `dotnet-isolated`. Local - `UseDevelopmentStorage=true` requires Azurite; configure Azure host storage separately. - Use a local test private key for `EPP_DECRYPTION_KEY_PEM`; in Azure, use a Key Vault reference - and give the caller the matching public key. Core Tools does not resolve Key Vault references - locally. `EPP_ENCRYPTION_KEY_ID` is advisory only; this sample has one decryption key, not - multi-key rotation. The decryption key, provider credentials, and caller authentication are separate. - - For local work, keep the host **loopback-only**, without tunnels or public forwarding. Core Tools - has no Easy Auth, and managed identity cannot use your CLI login. Use offline tests or evaluation - mode by default; do not add a production credential fallback merely to test locally. - -4. **Deploy the Functions.** - - - Configure **App Service Authentication (Easy Auth)** before exposing the endpoint. It is the - only caller-authentication gate; the Function handler is anonymous and has no backup validator. - Never use `-NoEasyAuth`, trust forwarded principal headers, or treat JWE/nonce proof as caller - authentication. Anyone with the public encryption key can create a JWE request. - - | Easy Auth setting | Required value | - |---|---| - | `globalValidation.requireAuthentication` | `true` | - | `globalValidation.unauthenticatedClientAction` | `Return401` | - | `httpSettings.requireHttps` | `true` | - | `identityProviders.azureActiveDirectory.registration.clientId` | Endpoint app's client ID | - | `identityProviders.azureActiveDirectory.registration.openIdIssuer` | Trusted tenant issuer matching the caller's token version; never `common` or `organizations` | - | `identityProviders.azureActiveDirectory.validation.allowedAudiences` | Exact endpoint-app audience agreed with SAS | - | `identityProviders.azureActiveDirectory.validation.defaultAuthorizationPolicy.allowedApplications` | Nonempty allowlist of authorized SAS caller application IDs, not the endpoint app ID | - | `globalValidation.excludedPaths` | No exemption for `/api/SendOtp` or an alternate ingress route | - - For v1 tokens, use the agreed Application ID URI audience and `https://sts.windows.net/{tenantId}/` - issuer; for v2, use the matching v2 issuer and agreed audience, normally the endpoint app client-ID - GUID. Do not derive trust from request `tenantId`. Key Vault RBAC for the Function identity is - separate from authorizing SAS callers; this code does not check an Entra application role. - - Leave the endpoint app registration's `tokenEncryptionKeyId` **null**: encrypted Entra access - tokens are not supported. If correcting an existing registration, do not delete certificates or - change signing keys; request a fresh token afterward. This does **not** disable the required - JWE encryption of `encryptedDeliveryContext` or remove `EPP_DECRYPTION_KEY_PEM`. - - Download the selected language's [Function ZIP](../TECHNICAL.md#download-a-function-zip) from GitHub - Releases, or use the [root-level packaging scripts](../TECHNICAL.md#build-zips-locally) for custom builds. - The .NET source ZIP must be extracted and built/published with the .NET 8 SDK or a build-enabled - deployment pipeline. The Python source ZIP requires Azure remote build on Linux. Neither source - ZIP is ready for direct run-from-package. Downloading or building a ZIP does not deploy it. - - Build and publish only the selected language folder with its runtime dependencies, not the - repository root or stale output. Inspect the package: exclude local settings, private keys, - credentials, tests, and diagnostic scripts using the runtime's `.funcignore` and publish rules. - Apply the settings from step 3 to the Function App's Azure environment; local settings are not - published automatically. Verify managed identity access and keep public ingress disabled - until authentication is configured. Source changes do not update an existing deployment. - - The root [.gitignore](../.gitignore) covers all runtimes; publishing uses separate exclusions in - [JavaScript](../javascript/.funcignore), [Python](../python/.funcignore), and [.NET](../dotnet/.funcignore). - The [.NET project](../dotnet/dotnet.csproj) also excludes local settings from publish output. - -5. **Validate.** - - - Send an authorized `POST /api/SendOtp` with a valid encrypted envelope and `mode: 2` or - `mode: "evaluation"`. Expect `200` and the matching nonce, with no provider lookup, provider - secret reads, or outbound provider HTTP. See the [evaluation contract](CONTRACT.md#evaluation-generic-shutter). - - Before live testing, verify these cases on the deployed endpoint, not just in offline tests: - - | Check | Expected result | - |---|---| - | Missing, malformed, expired, or invalidly signed caller token | `401` before the handler | - | Wrong issuer/audience or caller outside the allowlist | Rejected before the handler | - | Authorized caller and valid evaluation envelope | `200` with matching nonce, no provider I/O | - | Alternate routes, hostnames, and serving slots | No authentication or HTTPS bypass | - - After those checks pass, confirm the destination and channel, use `mode: 1`, and submit once. - `200` with a matching nonce confirms provider acceptance, **not handset delivery**. Confirm receipt - and spoken digit clarity through the handset/provider reports. The message is forwarded unchanged. - Do not blindly retry a timeout: the provider may already have accepted the request. Review the - [production limitations](CONTRACT.md#production-limitations), including no durable handoff, - early acknowledgement, expiry enforcement, or multi-key rotation. - - Never record phone numbers, messages, nonce values, tokens, API keys, encrypted request bodies, - or raw provider responses in reports. Keep platform/SDK body tracing off. Repeat the deployed - checks after deployment, authentication changes, and slot swaps. Local evaluation and passing - unit tests do not certify platform authentication or live delivery. - - For diagnostics, find the `logType: "request"` summary and join its separate service events by - `functionRequestId`. Inspect `failureStage`, `failureReason`, `httpStatus` and `providerHttpStatus` - rather than enabling payload tracing. Raw Microsoft `x-ms-client-request-id` / `x-ms-correlation-id`, - provider `providerMessageId`, and host `functionInvocationId` have different sources; see - [application logs](CONTRACT.md#application-logs) for their meanings and collection/sampling caveats. - The service events show safe envelope metadata, the OAuth-app or Key Vault credential path, - request construction, provider send/response, and `response_prepared`. The latter records status - and whether the response contains nonce/correlation fields, not their values; it is not proof the - caller received the response. Credential resolution can use caches. `providerEndpoint` contains the - base URL and API path only, without query strings, userinfo or fragments; support IDs remain raw - so customers can share the exact reference with Microsoft/provider support. - - Provider credentials are [prewarmed and refreshed per worker](CONTRACT.md#credential-caching-and-refresh) - automatically when a provider is configured. This contacts Key Vault or Entra without sending an OTP. - Evaluation requests still skip those dependencies, but independent background preparation may run - alongside them. For local evaluation-only use without managed identity, leave `EPP_PROVIDER_NAME` - unset. Check for `credential_refresh_failed` warnings before live testing. - - Compare fresh-worker, warm, expiry/rotation and concurrent-request behavior. A warmup or passing - offline test does not prove the first live request fits the caller's timeout. Background refresh - does not retry or deduplicate a provider send. Do not rerun provisioning or change FIC, app - registration, provider settings or decryption keys to deploy this code-only improvement. +# Customer configuration and validation + +Start with the [root onboarding checklist](../README.md). This is the detailed customer runbook +for values, provider access, and acceptance checks; [the setup guide](../setup/docs/README.md) +covers workstation preparation and the deployment command. + +**The normal path is: confirm access, register an app, run setup once, complete provider +authentication, validate, then activate policy.** Setup already builds/publishes the Function and +configures its Azure settings. Local settings, Core Tools, and a second manual deployment are +**not** required. [Optional developer work](#optional-local-development-and-manual-deployment) +is separate. + +## 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. + +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. + +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. + +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. + +## Values and ownership + +### 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. + +| Input | Where to find or decide it | Where to set it / required for | +|---|---|---| +| Customer tenant ID | Microsoft Entra admin center > Overview > Tenant ID; must contain the dedicated app and the Azure identities. | `-TenantId`; all deployments. Not the provider tenant ID. | +| Azure subscription ID | Azure portal > Subscriptions > selected subscription > Overview. Confirm its directory is the customer tenant. | `-SubscriptionId`; all deployments. Not a tenant ID. | +| Endpoint application client ID | Entra > App registrations > the dedicated app > Overview > Application (client) ID. Select the registration by this ID, not just its display name. | `-ApplicationId`; all deployments. **Not** the app Object ID or Enterprise application's Object ID. | +| Azure region | Subscription's available Linux FC1/EP1 regions and quota, plus your organization's requirements. | `-Location`, for example `westus2`; all deployments. | +| Provider / channel | Provider's approved account integration: guided catalog supports `telesign` or `soprano`, and one `sms` or `voice` route. | `-Provider` and `-Channel`; all deployments. | +| Provider route label | Provider-confirmed Global/EU selection; see the caveat below. | `-EndpointRegion global` or `eu`; all deployments. The prompt calls this **Tenant scope**. | +| Language / hosting | One of `javascript`, `dotnet`, `python`; FC1 or EP1 after reviewing billing and cold starts. | `-Language`, `-ServicePlan`; all deployments. | +| Resource prefix | A unique deployment label, 2-8 lowercase letters/digits, starting with a letter. | `-ResourcePrefix`; all deployments. Keep it with your inventory for reruns. | +| 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. + +**One guided deployment has one provider and one channel.** The resource-name suffix uses the +subscription ID, app client ID, and prefix, not the provider, channel, or Azure region. Rerunning +with those same identifiers and changing SMS to voice updates the existing deployment; it does +not add another endpoint. For an independent channel/provider/region deployment, use a distinct +prefix and a new dedicated app, then validate it before changing policy. Reusing an existing +app across new vaults can fail its encryption-key continuity checks after resources have changed. +The [manual Front Door design](FRONTDOOR.md) requires coordinated same-key regional origins, +not independent setup runs. + +### 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`, `channel`, and `endpointRegion` 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. + +| Value or setting | Source / verification | Customer action | +|---|---|---| +| `EPP_PROVIDER_NAME`, `EPP_PROVIDER_CHANNEL`, `EPP_PROVIDER_ENDPOINT_REGION`, `EPP_PROVIDER_AUTH_MODE` | Selected profile: Telesign `apiKey`, Soprano `oauth`. | Compare with approved provider/channel. | +| `EPP_PROVIDER_ENDPOINT` | Full selected provider send URL; setup writes the complete path. | Have the provider confirm account access. Do not append another API path or substitute an arbitrary test URL. | +| `EPP_PROVIDER_TENANT_ID` | Profile's provider directory; also appears as `providerTenantId` in the summary. | Do not replace it with the customer's tenant ID. Used for provider tenant restriction and Soprano OAuth authority. | +| `EPP_PROVIDER_APP_ID`, `EPP_PROVIDER_SCOPE` | Soprano API resource client ID and exact `api://.../.default` scope from the profile. | Provider administrator confirms these; API-key providers do not need them. The app ID is setup metadata; the runtime requests the configured scope. Neither is the endpoint app ID. | +| `EPP_OUTBOUND_CLIENT_ID` | Dedicated endpoint application's **client ID**, reused as the Soprano calling app. | Do not create a second client secret or substitute the Soprano API app ID. | +| `EPP_OUTBOUND_MI_CLIENT_ID` | Setup-created outbound user-assigned managed identity > Overview > Client ID. | Soprano token exchange uses this ID. Its Principal/Object ID is different and is used by the federated credential's subject. | +| `KEY_VAULT_URL` | Summary's `resources.keyVault`; vault Overview > Vault URI. | Put Telesign credentials in this vault. Setup grants its Function system identity Key Vault Secrets User. | +| `EPP_DECRYPTION_KEY_PEM`, `EPP_ENCRYPTION_KEY_ID` | Versioned Key Vault reference and registered encryption credential ID. Summary includes certificate/secret identifiers and expiry, **not** private-key bytes. | Do not view/copy the private key. Assign a [renewal owner](../setup/docs/README.md#encryption-certificate-lifecycle). | +| `EPP_PROVIDER_TIMEOUT_MS`, `EPP_PROVIDER_RETRY_INTERVAL_MS` | Profile timing values. Runtime provider HTTP timeout is capped at 2500 ms. | Neither is a whole-request deadline; retry interval metadata does **not** enable send retries. | +| `EPP_KEY_VAULT_CACHE_ENABLED`, `EPP_ACCESS_TOKEN_CACHE_ENABLED` | Setup writes `false` for FC1, `true` for EP1. | Current checked-in runtimes do not read these switches. Verify the selected release before assuming cache control; see [plan guidance](../setup/docs/README.md#service-plan-selection). | +| Application Insights, storage, runtime/package settings and identities | Created/configured for the selected plan; system identity handles vault/storage/telemetry, outbound identity handles Soprano exchange. | Verify telemetry ingestion. Do not copy local emulator settings or EP1-only settings into FC1. | +| Inbound caller issuer, audience and allowlist | Function App > Authentication; setup configures Easy Auth for the Microsoft phone-provider caller. | Read back platform authentication, not just `EPP_EXPECTED_*` metadata. App settings are not an alternative caller-authentication gate. | + +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. + +## Complete provider authentication + + +Guided setup currently supports **Telesign and Soprano only**. The other bundled adapters are +developer integrations, not additional guided provider offers: + +| Provider | Credential names / mode | Deployment support | +|---|---|---| +| `telesign` | `telesign-api-key` and `telesign-customer-id`; Basic authentication | Guided; complete the vault steps below. | +| `soprano` | No API-key secrets; OAuth client assertion | Guided; complete the provider-admin handoff below. | +| `infobip` | `infobip-api-key`; `Authorization: App ...` | Adapter only; no guided profile. Validate its account/options separately. | +| `sinch` | `sinch-api-token`; static token | Adapter only; no guided profile. Validate its account/options separately. | + +### 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 EPP URL for the chosen channel. +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. Workers cache credentials; use the [runtime cache behavior](CONTRACT.md#credential-caching-and-refresh) + to plan first-use/rotation checks. Absence of a warning does not prove 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; +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 + +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. + +Send the following **identifiers only** through the provider's approved onboarding channel: +customer tenant ID; `applicationId` / `EPP_OUTBOUND_CLIENT_ID`; selected channel and full URL; +`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. + +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. + +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 +tenant-specific v2 issuer, and audience is `api://AzureADTokenExchange`. Do not substitute the +identity Client ID into the subject. These are customer-side checks only; they do not prove +provider-side consent. See [Microsoft's federation guidance](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-config-app-trust-managed-identity) +for the mechanism, then use an authorized controlled live test to verify the full integration. + +## 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, channel, provider, and +approved test window from your inventory/summary. Agree on controlled recipients privately +and obtain explicit permission before any paid/live send. + +| Order / owner | Check | Evidence to retain without sensitive payloads | +|---|---|---| +| Azure operator | Read back HTTPS/Easy Auth and verify every serving route/hostname/slot has the intended issuer, audience, caller allowlist, and no SendOtp exemption. | Configuration review with resource identity and UTC time. | +| Azure operator | Perform the missing-token negative check below. | HTTP status; a rejection is not a healthy evaluation. | +| Authorized test operator | Invalid/expired/signature/wrong-issuer/wrong-audience tokens and unauthorized callers fail before handler execution. Pair negative checks with valid evaluations to rule out a general outage. | Expected versus observed status, UTC time, safe request/correlation IDs. | +| Authorized test operator | Valid encrypted `mode: 2` / `"evaluation"` request returns `200` and the matching nonce. Tampered/invalid envelopes fail. | Nonce match **boolean**, status, latency, and correlated application events; never the nonce/body. | +| Authorized test operator + provider | Submit one approved `mode: 1` live test after evaluation succeeds. Verify provider acceptance and actual handset receipt/audio. | Acceptance status plus separate recipient/provider delivery confirmation. No phone number, OTP, or raw response. | +| Operations owner | Confirm request/log ingestion, correct runtime schema, and notifications. | Saved query/resource scope and tested action-group/alert routing; see [monitoring](MONITORING.md). | + +This bounded PowerShell check sends **no token and no OTP data**, and does not follow redirects. +Use the summary's exact HTTPS endpoint, never an example hostname or a provider URL: + +```powershell +$endpoint = 'https:///api/SendOtp' +$response = Invoke-WebRequest -Uri $endpoint -Method Post -ContentType 'application/json' ` + -Body '{}' -SkipHttpErrorCheck -MaximumRedirection 0 -TimeoutSec 30 +$response.StatusCode +``` + +On the default single-region Easy Auth configuration, expect `401`. A `403` may instead be a +network/access rejection and needs investigation; `200` or a handler validation `400` means +this did not demonstrate the expected authentication gate. Transport/redirect errors are not +passes. This only checks the missing-token case, not all authorization or readiness properties. + +Evaluation skips provider selection/credential lookup and provider HTTP **on its request path**. +Configured workers can independently acquire credentials at startup; JavaScript/Python also +poll for refresh. Do not confuse those background events with an evaluation sending a message. + +For live requests, `200` with matching nonce means **provider acceptance, not delivery**. +Soprano voice extracts the first six-digit sequence; Telesign voice paces standalone six-digit +runs and repeats the message. Verify the actual locale, spoken digits, and recipient experience. +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 + +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). +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. + +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. + +Assign ongoing owners for request failures/latency, provider account and credential rotation, +certificate expiry/renewal, alert routing, telemetry gaps, retention/cost, and policy rollback. +Setup creates telemetry resources, **not** alert rules, action groups, availability tests, +certificate contacts, or provider delivery receipts. Complete those manual operations before +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 + + +**Skip this section after guided setup unless developing a custom package.** Follow the selected +[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`. + +`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 +local work, omit provider settings so startup does not try to acquire managed-identity credentials. +Your CLI login cannot substitute for the runtime's managed identity. Bind the host to loopback, +without tunnels/public forwarding: Core Tools has no Easy Auth. Never copy the production +private key, telemetry credentials, or all cloud settings to a workstation. + +For custom cloud deployment, follow [packaging instructions](../TECHNICAL.md#download-a-function-zip) +and the selected plan's deployment mechanism. Python/.NET source ZIPs are not runnable +run-from-package artifacts. Exclude local settings, keys, tests, and diagnostic scripts using the +runtime's `.funcignore` and project publish rules. Source edits or a locally built ZIP do not +update Azure; apply approved cloud settings separately and rerun deployed acceptance checks. + + +### Manual deployment trust checklist + +Guided setup already configures these. A manual deployer must retain equivalent platform +protection **before opening ingress**; the anonymous handler has no backup token validator. + +| Easy Auth setting | Required value | +|---|---| +| `globalValidation.requireAuthentication` | `true` | +| `globalValidation.unauthenticatedClientAction` | `Return401` | +| `httpSettings.requireHttps` | `true` | +| `identityProviders.azureActiveDirectory.registration.clientId` | Endpoint app client ID | +| `identityProviders.azureActiveDirectory.registration.openIdIssuer` | Trusted tenant issuer matching token version; never `common` or `organizations` | +| `identityProviders.azureActiveDirectory.validation.allowedAudiences` | Exact endpoint-app audience agreed with the authorized caller | +| `identityProviders.azureActiveDirectory.validation.defaultAuthorizationPolicy.allowedApplications` | Nonempty authorized Microsoft caller app-ID allowlist, not the endpoint app ID | +| `globalValidation.excludedPaths` | No SendOtp or alternate-ingress exemption | + +For v1, use the agreed Application ID URI audience and `https://sts.windows.net/{tenantId}/` +issuer; for v2, use the matching tenant-specific v2 issuer and agreed audience, normally the +endpoint app client-ID GUID. Never derive trust from the request's `tenantId` or forwarded headers. +Leave the app registration's `tokenEncryptionKeyId` **null**: encrypted Entra bearer tokens are +not supported. This does not disable JWE payload encryption. Never delete encryption credentials +or change signing keys just to make token validation pass. diff --git a/dotnet/README.md b/dotnet/README.md index f1cd63a..0a53da4 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -5,6 +5,14 @@ selected provider per deployment. Target: .NET 8 isolated worker, Azure Function ## 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. @@ -85,7 +93,7 @@ Requests that bind successfully retain the application validation, PII-safe logg Use incoming `mode: 2` or `mode: "evaluation"` as the generic shutter for every provider: 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 forward the rendered message unchanged using the configured provider's API key or OAuth token and +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-generic-shutter). @@ -100,6 +108,11 @@ six-digit numeric run that is not part of a longer number and repeats the comple ## Source +For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and +[runtime-specific log discovery](../docs/MONITORING.md#runtime-specific-log-discovery). +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 that provider's credentials, builds and sends the common bounded HTTP request, asks the provider to deserialize its typed response DTO and return the final outcome, then maps that @@ -114,9 +127,10 @@ tenant or other request-aware selection. No router or routing configuration abst 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. Each shared acquisition owns -its cancellation budget, so a waiter cannot cancel another request's retrieval. Evaluation remains -independent. +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. | Source | Purpose | |---|---| diff --git a/javascript/README.md b/javascript/README.md index fbecfc9..5808c3c 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -7,6 +7,13 @@ each provider owns its credentials, wire request, response interpretation, outco ## 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. @@ -91,8 +98,8 @@ Easy Auth authenticates and authorizes the caller before `POST /api/SendOtp`; th validates the envelope and decrypts the JWE, without parsing or echoing incoming `Authorization`. JWE does not authenticate SAS: 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. The caller-rendered message is forwarded unchanged; -the endpoint does not guess a passcode. +environment settings or sources of identity trust. SMS forwards the caller-rendered message; +voice uses the provider-specific extraction/pacing described above. For non-delivery validation, use incoming `mode: 2` or `mode: "evaluation"`. This generic shutter works for every provider without provider configuration, provider Key Vault reads or provider HTTP; @@ -105,6 +112,10 @@ retries. The shared contract defines validation, HTTP outcomes and privacy-safe ## Source and extension points +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. + 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 diff --git a/python/README.md b/python/README.md index 4c1105c..fded75b 100644 --- a/python/README.md +++ b/python/README.md @@ -5,6 +5,14 @@ selected provider per deployment. Target: Python 3.11, Azure Functions v4, Pytho ## 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. @@ -79,7 +87,7 @@ authenticate SAS: anyone with the public key can encrypt a request, and a fixed Use incoming `mode: 2` or `mode: "evaluation"` as the generic shutter for every provider: 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 forward the rendered message unchanged using the configured provider's API key or OAuth token and +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-generic-shutter). @@ -94,6 +102,11 @@ six-digit numeric run that is not part of a longer number and repeats the comple ## Source +For deployed 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. + 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 diff --git a/setup/docs/README.md b/setup/docs/README.md index fb9932e..56003bb 100644 --- a/setup/docs/README.md +++ b/setup/docs/README.md @@ -6,6 +6,11 @@ PowerShell script to deploy the endpoint, and activate policy manually after val The customer does not clone this repository or download Bicep/support scripts separately. `Setup-Epp.ps1` retrieves those files and the selected provider's JSON from GitHub. +Start at the [customer checklist](../../README.md) and complete the +[access/eligibility gate and values worksheet](../../docs/ONBOARDING.md#before-purchasing-or-deploying) +first. Provider purchase is through [Microsoft Security Store](https://securitystore.microsoft.com/private-solutions); +purchase alone does not enable the Microsoft tenant feature or authorize the provider API. + This guide deploys **one Function endpoint in one Azure region**. It does not provision Azure Front Door or a second region. For the optional multi-region design, use the [manual Front Door onboarding guide](../../docs/FRONTDOOR.md). No Front Door setup script is @@ -14,10 +19,11 @@ Front Door origins. A successful deployment or evaluation doesn't prove live del ## Availability -Choose **SMS or voice**, a **Global or EU tenant scope**, **Telesign or Soprano**, and an +Choose **SMS or voice**, 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 private test branch can use its matching fork release. +`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. @@ -26,6 +32,10 @@ route URLs, tenant, authentication, and timings. Soprano contains its provider t routes, API application ID, scope, authentication, and timings. The provider files contain the complete deployment contract. +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. + To test unpublished upstream changes, publish them to a public fork with a matching stable package release, then use `-SourceRepository ` and `-SourceRef `. Both options must identify the same source as the downloaded @@ -47,6 +57,11 @@ usage beyond the grant, Key Vault, storage, telemetry, and provider services can FC1 can scale to zero and cold-start; these settings do not remove cold-start latency or force a new worker on every invocation. +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. + **Cache settings require runtime support.** Setup only writes the two app settings above; they do not change caching in a Function package that does not read them. Runtime cache-reader changes are separate and must be released and deployed before these settings take effect. Rerunning setup @@ -70,14 +85,18 @@ automatically migrate keys or activate the replacement endpoint. Use a dedicated nonproduction tenant/subscription for the first deployment. 1. In the customer tenant's **Microsoft Entra admin center > App registrations**, register a - dedicated organizational application. No redirect URI, client secret, API permission, app role, + dedicated application 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. 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, account/sender registration, and onboarding for the selected adapter. +3. Complete provider purchase and account/sender registration, and arrange API onboarding for the selected adapter. 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. + 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 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 @@ -126,6 +145,41 @@ disclosed outbound managed-identity federated credential. - **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. +### 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). + +Azure roles do not grant Entra/Graph permissions, and Entra roles do not grant Azure/vault access. +The Entra Privileged Role Administrator and delegated Graph scopes 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. + +### Install and check workstation tools + +Use the official [PowerShell installation](https://learn.microsoft.com/powershell/scripting/install/installing-powershell-on-windows), +[Azure CLI installation](https://learn.microsoft.com/cli/azure/install-azure-cli-windows), 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: + +```powershell +$PSVersionTable.PSVersion +az version +Get-Module -ListAvailable Microsoft.Graph.Authentication, Microsoft.Graph.Applications | + Select-Object Name, Version +# Only when choosing dotnet: +dotnet --list-sdks +``` + +Missing Graph modules or Bicep can be installed by interactive setup after confirmation. +These version checks do not establish Azure permissions, quota, feature availability, or provider access. + | Choice | Azure runtime | Automatic deployment path | |---|---|---| | JavaScript | Node.js 22, Functions v4 | Verify and publish the ready ZIP with its production dependencies | @@ -137,11 +191,9 @@ verification. Source and deployed-package hashes are recorded separately when a 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 normally detects these automatically. For unattended execution, allow installation explicitly: - -```powershell -.\Setup-Epp.ps1 -NonInteractive -InstallPrerequisites ... -``` +Setup normally detects these automatically. For noninteractive execution, preinstall prerequisites +or explicitly allow missing Graph/Bicep installation with `-InstallPrerequisites`; see the +[complete example](#noninteractive-example). Install Azure CLI through its official installation instructions if necessary. Setup checks the explicitly supplied subscription and tenant without changing the CLI's selected subscription. If no @@ -179,7 +231,7 @@ The flow is: 1. **Collect missing customer inputs:** tenant, subscription, existing application client ID, Azure region, and resource prefix. Supplied values are reused without prompts. Credentials are never requested as ordinary string parameters. -2. **Choose SMS or voice**, then the **Global or EU tenant scope**. +2. **Choose SMS or voice**, then the **Global or EU provider route** (shown as **Tenant scope**). 3. **Choose a provider**, then an Azure Function **platform**: Node.js, .NET, or Python. Setup downloads the provider JSON, resolves one complete route containing endpoint, authentication, app-ID/scope when applicable, timeout, and retry interval. Explicit test values are allowed, shown as test @@ -225,16 +277,24 @@ Supply known values to shorten the prompts: ```powershell .\Setup-Epp.ps1 ` - -TenantId ` - -SubscriptionId ` - -ApplicationId ` + -TenantId '' ` + -SubscriptionId '' ` + -ApplicationId '' ` -Location westus2 ` -Language javascript ` -ServicePlan FC1 ` -Provider telesign ` + -Channel sms ` + -EndpointRegion global ` -ResourcePrefix contoso ``` +Replace the ID placeholders with quoted GUID strings before execution. For an independent +second channel/provider deployment, use a different prefix **and a dedicated app**. +The suffix does not include channel/provider/region, so changing those inputs with the same +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 @@ -273,6 +333,67 @@ authenticate both clients 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. +### Noninteractive example + +This is a fully specified Telesign/SMS/Global JavaScript deployment example, not a credential +script. Replace the three IDs and choose a permitted region/prefix/plan before running. +The preliminary user sign-ins can require MFA and consent; `-NonInteractive` does not create a +headless service-principal deployment path. Install the Graph modules/Bicep beforehand using +interactive setup's prerequisite prompts or your approved installation process. + +```powershell +$tenantId = '' +$subscriptionId = '' +$applicationId = '' + +az login --tenant $tenantId +if ($LASTEXITCODE -ne 0) { throw 'Azure sign-in failed.' } +Connect-MgGraph -TenantId $tenantId -ContextScope Process ` + -Scopes 'User.Read', 'Application.ReadWrite.All', 'Application.Read.All', 'AppRoleAssignment.ReadWrite.All' + +.\Setup-Epp.ps1 ` + -TenantId $tenantId ` + -SubscriptionId $subscriptionId ` + -ApplicationId $applicationId ` + -Location westus2 ` + -Provider telesign ` + -Channel sms ` + -EndpointRegion global ` + -Language javascript ` + -ServicePlan FC1 ` + -ResourcePrefix contoso ` + -OutputDirectory .\epp-output ` + -NonInteractive ` + -ApproveDeployment +``` + +Use `-InstallPrerequisites` only when authorizing automatic missing-dependency installation. +Use the [source/version options](#source-versioning) to pin repeatable deployments. +Review the corresponding interactive plan first; `-ApproveDeployment` authorizes the complete +mutation set, not just package upload. Do not use it as a dry run. + +### Read the deployment summary + +Open the timestamped JSON under `epp-output` in a private editor. It contains identifiers and +configuration metadata, not provider credentials or private-key bytes. Keep it access-controlled. + +| Summary field | How to use it | +|---|---| +| `tenantId`, `subscriptionId`, `applicationId` | Verify the intended customer directory, Azure subscription, and endpoint **client ID**. | +| `resources.functionApp`, `resources.keyVault`, `resources.applicationInsights`, `resources.logAnalytics` | Find the exact Azure resources; do not guess names or use a different environment's vault. | +| `provider`, `channel`, `endpointRegion`, `providerTenantId` | Confirm provider routing, separate from the customer's directory and Azure location. | +| `endpointUrl`, `identifierUri` | Exact SendOtp URL and registered app audience information for the authorized test/policy operator. | +| `encryptionKeyId`, `certificateThumbprint`, `certificateId`, `certificateSecretId`, `certificateExpiresUtc` | Public-key identification and version/renewal inventory. A secret **ID** is not its value; do not fetch its private-key contents. | +| `endpointServicePrincipalId`, `microsoftPhoneProviderServicePrincipalId` | Tenant-local enterprise-application Object IDs; neither is the endpoint app client ID. | +| `source`, `packageUrl`, `sourcePackageSha256`, `packageSha256`, `language`, `servicePlan` | Source/package provenance and runtime/plan. FC1 Python's built hash can be null; source and built hashes describe different artifacts. | +| `policyChanged` | Expected `false`: deployment has not activated policy. | + +For example, a healthy record can contain `"provider": "telesign"`, `"channel": "sms"`, +`"endpointRegion": "global"`, `"certificateRenewal": "manual"`, and `"policyChanged": false`. +Those fields do not prove credentials, delivery, or monitoring work. Follow +[provider authentication](../../docs/ONBOARDING.md#complete-provider-authentication) next, +**even if the script's completion text does not prompt for your provider**. + ### Encryption certificate lifecycle The issuance policy uses **12-month validity, key reuse, and manual renewal**. It specifies @@ -280,6 +401,14 @@ The issuance policy uses **12-month validity, key reuse, and manual renewal**. I 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. +In **Key Vault > Certificates > Certificate contacts**, add the approved operations/renewal +contact and verify the address and ownership. Portal labels can vary; follow +[certificate renewal and contacts](https://learn.microsoft.com/azure/key-vault/certificates/overview-renew-certificate). +Record `certificateExpiresUtc` in your inventory and create an independent scheduled reminder +before the 30-day window, with an escalation owner. EmailContacts is not an Azure Monitor alert +rule, and setup does not test email delivery. Monitor both the vault certificate's and Entra +credential's expiration; renewing one does not renew the other automatically. + Reruns reuse a valid matching cloud certificate. Only a certificate-not-found response triggers `az keyvault certificate create`; Azure CLI waits for self-signed issuance. Other errors, including pending-operation conflicts, stop setup rather than starting a custom recovery workflow. Disabled, @@ -339,27 +468,62 @@ RBAC propagation, Key Vault issuance, Entra behavior, or provider delivery. ## Step 3 - manually validate and activate policy -1. Save the Step 2 summary and confirm its tenant, application client ID, endpoint URL, encryption - key ID, and certificate with the EPP onboarding owner. **Replace all test provider values** and - provision the adapter-named API credentials in Key Vault. Verify the package's channel routing - and retry behavior. -2. Validate the deployed endpoint with synthetic, non-delivering evaluation requests first. - Missing/invalid credentials and unauthorized callers must be rejected by Easy Auth. An admitted - caller's valid encrypted request must return the matching nonce. Then verify live SMS/voice - provider acceptance and handset delivery through the supported test procedure. Never put - phone numbers, messages, tokens, private keys, or nonce values in shared logs. -3. An **Authentication Policy Administrator**, using the approved Microsoft Graph tool and delegated - `Policy.ReadWrite.AuthenticationMethod`, must read the selected channel configuration: - `https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/Sms` - for SMS or the same path ending in `/Voice` for voice. If the selected configuration or its - `url` and `appId` properties are unavailable, stop and obtain the supported onboarding procedure - from Microsoft rather than sending a guessed update. -4. Save the existing channel configuration with the tenant ID and timestamp. Re-read it immediately - before a manual change, stop if it changed, and use `If-Match` when an ETag is available. -5. Update `url` with the highlighted Function endpoint and `appId` with the highlighted endpoint - application client ID printed by setup. Preserve all other properties, then read the configuration - back and compare those values before considering activation complete. - -Policy activation, policy backups, and policy rollback are administrator-owned manual operations. -No policy API is called by the setup package. For rollback, restore only the reviewed prior EPP -value through the still-supported contract; resource deletion is not a policy rollback. \ No newline at end of file +First complete [provider authentication and the deployed acceptance checks](../../docs/ONBOARDING.md#complete-provider-authentication). +The customer onboarding owner must arrange an authorized Microsoft EPP caller; this repository's +offline tests and an ordinary Azure CLI token do not supply that caller. + +**Activation is an assisted, product-specific gate, not an executable public Graph recipe.** +The public beta [SMS](https://learn.microsoft.com/en-us/graph/api/resources/smsauthenticationmethodconfiguration?view=graph-rest-beta) +and [Voice](https://learn.microsoft.com/en-us/graph/api/resources/voiceauthenticationmethodconfiguration?view=graph-rest-beta) +resource definitions do **not** document EPP `url` and `appId` fields. The general method +configuration APIs alone do not establish EPP feature availability or an EPP update contract. +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. +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. + +Once that procedure is available: + +1. Save the existing selected-channel configuration, including EPP settings if present, target + groups, state, and other properties, with the tenant ID, UTC time, and change owner. Protect + the snapshot as tenant configuration. Agree on a maintenance window and rollback trigger. +2. Re-read immediately before changing; stop if another administrator changed the policy. + Use conditional updates only where the approved endpoint documents their support. +3. Apply only the reviewed EPP changes using `endpointUrl` and `applicationId` from the saved + deployment summary in the **approved field locations**. Preserve other policy properties + and group targeting. Setup's endpoint Service Principal Object ID is not `applicationId`. +4. Read back and compare both changed values and preserved settings. Have the authorized + operator verify the selected channel through an actual controlled Entra flow; record + acceptance and separate recipient delivery, not secrets or OTPs. +5. If validation fails, execute the agreed rollback below. Do not leave a broken channel + activated while deleting its resources or changing authentication to debug it. + +### Rollback and decommissioning + +Policy rollback, package/configuration rollback, and resource deletion are different operations. +Setup does none of them automatically. + +1. The policy administrator restores only the reviewed prior EPP configuration through the + approved procedure (or its documented removal operation if there was no prior EPP endpoint). + Preserve unrelated settings, read back, and validate the restored authentication route. + Keep the old endpoint available until traffic has safely moved. +2. For an endpoint release rollback, use the retained package/source version and reviewed + settings. Do not blindly rerun setup on an active app: it can mutate Entra, reset settings, + and disable ingress. Coordinate certificate/key continuity; never delete credentials to + bypass a mismatch. Repeat the authorized evaluation and live checks. +3. Only after policy/traffic and rollback retention are confirmed, have the Azure owner inventory + and retire the dedicated resource group and any separately created monitors/Front Door resources. + Confirm exact subscription/resource IDs and dependencies before deletion. Respect vault + soft-delete/purge protection and approved log/certificate retention; do not purge for a rerun. +4. Have the Entra/provider administrators separately review app registrations, enterprise + applications, app-role assignments/consents, federated credentials, and provider subscriptions. + Azure resource deletion does not remove these. The Microsoft phone-provider service principal + and its Graph grant may be shared with other endpoints: do not delete/revoke shared objects + without an impact review. +5. Confirm billing and provider subscription status, retain required audit evidence, and close + alerts only after decommissioning. Stopping a Function or removing policy alone is not teardown. \ No newline at end of file diff --git a/setup/docs/Troubleshooting.md b/setup/docs/Troubleshooting.md index 86c244a..965621e 100644 --- a/setup/docs/Troubleshooting.md +++ b/setup/docs/Troubleshooting.md @@ -1,4 +1,52 @@ -# Troubleshooting Step 2 +# Setup and onboarding troubleshooting + +Return to the [customer checklist](../../README.md) for the complete path. This guide separates +setup failures from provider, validation, and telemetry failures; a successful deployment is +only one onboarding gate. + +## 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. + +| Symptom | First checks / owner | Do not do this | +|---|---|---| +| 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. | +| Endpoint returns 401/403 with no invocation | Azure operator checks Easy Auth, issuer/audience/caller restrictions, network access, and available platform/auth logs. An ordinary customer CLI token is not the allowlisted Microsoft caller. | Do not disable Easy Auth, add a test caller, or interpret rejection as readiness. | +| App failure after invocation, including 401 | Inspect correlated `request_failed` and provider-response events. A provider's 401/403 is mapped to a handler 401; this differs from Easy Auth rejection. | Do not change inbound trust to fix outbound provider authorization. | +| `decryption_failed` or unresolved key reference | Compare summary certificate/thumbprint/key ID and versioned reference; verify Function identity vault access and reference resolution, then use an authorized evaluation. | Do not export the private key, delete registered certificates, or assume new vault versions automatically update Entra. | +| Provider credentials unavailable | Check secret names/enabled versions/vault access for Telesign; customer federation plus provider-side authorization/tenant/scope for Soprano. Allow RBAC propagation. | Do not assume evaluation validates outbound credentials. | +| Timeout, 502/504, or provider acceptance without handset receipt | Separate credential, outbound HTTP, host, and caller timings; ask the provider for supported delivery evidence. Confirm locale/digit clarity for voice. | Do not blindly resend: the first request may already have been accepted. | +| No requests/traces in Application Insights | Check the exact component/workspace, UTC window, actual authorized traffic, host/worker startup, ingestion identity/network, filters/sampling/caps, and effective exporter. Use [collection guidance](../../docs/APPLICATION-INSIGHTS.md). | Do not treat an empty table/Failures view as healthy or search every runtime for a JSON request summary. | +| 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. + +## Recover without destructive cleanup + +Before approval, setup's read-only checks can fail without Azure resource mutation (local module +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/channel/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. ## Optional Front Door deployments @@ -96,7 +144,7 @@ Query parameters are passed in a file to avoid Windows command-shell escaping pr The actual deployment remains **EP1**; it is not changed to a Dedicated App Service Premium SKU. The accompanying 32-bit Python cryptography message is a performance warning, not the cause of -the invalid-SKU error. Rerun with the updated test-branch helper; changing the SKU or installing +the invalid-SKU error. Rerun with the reviewed current-main helper; changing the SKU or installing another Python runtime is not required to fix this check. ## A required Azure resource provider is not registered @@ -292,6 +340,9 @@ Keep Easy Auth enabled. Check the trusted tenant, actual token version, audience 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, replace dummy endpoints and configure the provider's exact Key Vault secret -names. Test with synthetic evaluation requests before live messages. EPP policy remains a -separate, administrator-approved manual operation; no setup code updates it. +For live delivery, confirm the setup-selected complete provider URL and its account authorization. +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.