Skip to content

Latest commit

 

History

History
628 lines (518 loc) · 40.1 KB

File metadata and controls

628 lines (518 loc) · 40.1 KB

EPP endpoint setup reference

Documentation | First deployment | Troubleshooting

This reference covers setup choices, reruns, and maintenance. For a first deployment, follow the five-step guide above; it handles the required source and Bicep files.

Task Section
Prepare access, tools, and hosting Prerequisites, permissions, plans
Register the dedicated app Step 1
Run or review setup Step 2, certificate choice, cancel at approval
Use known inputs and versions Noninteractive example, source versioning
Find deployment results Summary fields
Validate, activate, and maintain Step 3, renewal, rollback
Develop setup changes Offline checks

Confirm provider access and Microsoft's approved procedure before deployment.

Setup creates one regional endpoint. See Front Door for regional continuity.

Availability

Choose Telesign or Soprano, Global/EU provider routing (prompt: Tenant scope), Node.js/.NET/Python, and FC1/EP1 hosting. Setup verifies SHA256SUMS.txt, builds as needed, and publishes; no package URL/hash input is needed. See source/package selection.

Both channels are configured. Profile objects deployment.routes.global and deployment.routes.eu define smsEndpoint, voiceEndpoint, tenant, authentication, and timing values. Setup writes the endpoints to EPP_PROVIDER_SMS_ENDPOINT and EPP_PROVIDER_VOICE_ENDPOINT; Soprano also supplies its API app ID and scope.

Use a package that supports channel-specific settings; neither channel falls back to the other URL.

The current profiles use identical Global/EU URLs, and Soprano uses the same app ID/scope. The route label is not a residency or Azure-region selection. Infobip and Sinch are bundled adapters without guided profiles.

Service plan selection

Setup offers these two Linux hosting plans, with the same cache app settings for every language:

Option Hosting EPP_KEY_VAULT_CACHE_ENABLED EPP_ACCESS_TOKEN_CACHE_ENABLED
1. Flex Consumption (FC1) On-demand with a free usage grant; zero always-ready instances, 2048 MB, up to 40 on-demand instances "false" "false"
2. Premium (EP1) Existing Premium configuration with one warm instance "true" "true"

FC1 on-demand compute has a free usage grant; usage beyond the grant, Key Vault, storage, telemetry, and provider services can incur charges. FC1 can scale to zero and cold-start.

EP1 allocates paid warm capacity without OTP traffic. Budget for the plan, storage, Key Vault, telemetry, and provider services. Budget alerts do not stop spending.

Each switch controls one provider credential cache; unset defaults to true. Disabled caches acquire credentials per live request. Values are trimmed, case-insensitive true or false; restart after edits. Rerunning setup restores the plan defaults. Older packages ignore these switches.

Use -ServicePlan FC1 or -ServicePlan EP1 to skip the prompt. Unattended setup requires it.

Use a different resource prefix to change plans. In-place FC1/EP1 migration is not supported. Preflight rejects a mismatch with the existing resource-group tag or actual hosting-plan SKU, including older EP1 deployments without the new tag. Existing EP1 deployments can be rerun with -ServicePlan EP1. For an active endpoint, use a new Step 1 app or coordinate key continuity. Reusing an app still requires an explicit certificate choice; replacement affects every endpoint sharing the app.

Step 1 - manually create the application

For the first deployment, use a nonproduction tenant and subscription.

  1. In the customer tenant's Microsoft Entra admin center > App registrations, register a new application for this endpoint with Accounts in this organizational directory only as its initial supported account type; setup changes it to organizational multi-tenant after approval. No redirect URI, client secret, API permission, or app role is required manually.
  2. Record the Directory (tenant) ID and Application (client) ID. The script requires the client ID, not the application's object ID, and will not create a replacement registration.
  3. Complete provider purchase, sender registration, and API onboarding. Telesign uses telesign-api-key and telesign-customer-id in Key Vault. Soprano uses OAuth client-assertion exchange with the selected provider tenant/scope/application ID. Setup does not grant provider API consent or application roles. Finish the provider authentication handoff after setup creates the vault and identity identifiers needed for those steps.

Setup configures:

  • Organizational multitenancy and the Microsoft Entra ID allowed-tenants preview: home tenant, selected provider tenant, and preserved existing allowed provider tenants.
  • The app's enterprise application with assignment required, Epp.Invoke, and its assignment to the Microsoft phone-provider service principal.
  • That Microsoft principal's tenant-wide Graph Application.Read.All, identifier URI, public JWE certificate, and Easy Auth caller restriction.
  • The disclosed outbound managed-identity federated credential for Soprano.

Prerequisites for Step 2

  • Azure Cloud Shell in PowerShell mode, or PowerShell 7+, with an Azure CLI user login. Certificate issuance stays in Key Vault; no local certificate-store cmdlets are needed.
  • Azure CLI 2.60.0+ for FC1 or 2.48.1+ for EP1 on PATH, with GitHub, Azure, Microsoft Graph, Key Vault, and required SCM access. Setup can install Bicep after confirmation, but Azure CLI must already be installed.
  • An Azure user allowed to deploy at subscription scope and create scoped role assignments. Bicep grants Key Vault Certificates Officer and Key Vault Secrets Officer on this vault.
  • Certificate reuse: the operator needs source-vault ARM read, certificate read, and secret-version metadata read. A different vault also requires permission to grant the Function secret read through RBAC or an access policy. The vault must be in the customer tenant; use -KeyVaultSubscriptionId for another subscription. Setup never reads the private key locally.
  • A Microsoft Entra Privileged Role Administrator for granting the Microsoft first-party service principal Graph Application.Read.All. Microsoft Graph operations use the current Azure CLI user session through az ad and az rest; Azure subscription roles alone do not grant these directory permissions.
  • Microsoft Graph beta access for signInAudienceRestrictions. The provider tenant is added; Microsoft Entra ID always allows the home tenant. See the allowed-tenants schema.
  • The selected Linux FC1 or EP1 plan available in the region with enough quota. Resolve SubscriptionIsOverQuotaForSku before retrying. The operator needs subscription-scoped /register/action.
  • .NET only: install the .NET 8 SDK and allow NuGet access. Setup publishes for linux-x64, packages, and deploys automatically.
  • Python: Azure builds Linux dependencies; no local Python/pip install is required. The source archive is not a run-from-package artifact.

Permissions are separate

Owner at the selected subscription includes deployment, provider registration, and role-assignment rights. Another option is Contributor plus Role Based Access Control Administrator at the required scope, subject to any role-assignment conditions. Setup deploys at subscription scope and creates a new resource group, so access to an unrelated group does not cover the deployment. Activate eligible PIM roles before setup. See Azure roles.

Azure roles do not grant Entra/Graph permissions, and Entra roles do not grant Azure/vault access. The signed-in user needs the listed Entra roles for setup. An Authentication Policy Administrator handles activation separately. Setup grants vault roles only to its operator and Function identity.

Install and check workstation tools

Install missing tools using PowerShell installation, Azure CLI installation, and .NET 8 SDK download instructions (SDK only for C#). Open a new PowerShell 7 window after installation, then check:

$PSVersionTable.PSVersion
az version
# Only when choosing dotnet:
dotnet --list-sdks

Interactive setup can install missing Bicep.

Choice Azure runtime Automatic deployment path
JavaScript Node.js 22, Functions v4 Verify and publish the ready ZIP with its production dependencies
.NET .NET 8 isolated, Functions v4 Verify source ZIP, publish for Linux with .NET 8, repackage and publish
Python Python 3.11, Functions v4 Verify source ZIP; One Deploy with remote build on FC1, or validate/download and publish the SCM-built output on EP1

Setup records source and built-package hashes separately. FC1 Python leaves packageSha256 null because Azure builds the package.

For unattended execution, preinstall prerequisites or allow Bicep installation with -InstallPrerequisites. Azure CLI must already be installed. Sign-in, MFA, consent, and installation prompts are separate from deployment approval.

Use -ForceAuthentication for ambiguous cached identities. It requests device-code authentication, does not clear shared caches, and cannot be combined with -NonInteractive.

Step 2 - download and run one script

Download Setup-Epp.ps1 from the upstream raw URL:

Invoke-WebRequest `
    -Uri 'https://raw.githubusercontent.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/main/setup/Setup-Epp.ps1' `
    -OutFile .\Setup-Epp.ps1

Review the downloaded script before running it:

.\Setup-Epp.ps1

Force explicit account selection when testing on a shared or multi-account computer:

.\Setup-Epp.ps1 -ForceAuthentication

The flow is:

  1. Choose the customer Azure context. Setup lists cached enabled Azure CLI user subscriptions, then asks for the tenant and subscription. Refresh or start device-code sign-in when the expected account is missing. Explicit -TenantId and -SubscriptionId values are preserved.
  2. Choose the dedicated app registration and Azure region. The application picker shows organizational app names and client IDs, supports bounded name-prefix search and manual client-ID entry, and never substitutes an object ID. The region picker uses Azure's current Recommended metadata for a short list, with all-physical-region and manual-entry options. Supplied values skip these selectors.
  3. Choose the Global or EU provider route (shown as Tenant scope).
  4. Choose a provider by name, then an Azure Function platform: Node.js, .NET, or Python. When -Provider is omitted, setup reads the application's allowed-tenants configuration: no supported provider prompts for a name, one asks for confirmation, and several require a name. Existing allowed provider tenants are preserved; provider options are never numbered. The selected profile supplies both channel URLs and auth/timing values. The launcher has no endpoint/credential overrides or PackageUrl/PackageSha256 inputs. It uses packages/catalog.json and the latest stable CI release, or your -PackageReleaseTag epp-packages-<run>-<attempt>. Malformed/disabled profiles fail before resource creation.
  5. Service plan selection: choose Flex Consumption FC1 (free usage grant, zero always-ready instances, both cache settings "false") or Premium EP1 (warm instance, both cache settings "true"). Supply -ServicePlan FC1 or -ServicePlan EP1 to reuse a known selection.
  6. Enter a resource prefix, such as contoso. All resources created by the script start with this prefix. Use 2-8 lowercase letters or digits, starting with a letter. Every top-level resource name then adds the meaningful epp marker, for example contoso-epp-rg-<suffix>. A deterministic suffix derived from the subscription, application ID, and prefix reduces global-name collisions. Reruns use the same names.
  7. Check prerequisites and revalidate authentication. Missing Bicep can be installed after a separate confirmation. Graph operations reuse the selected Azure CLI user session; no Graph SDK installation or separate sign-in occurs.
  8. Choose encryption certificate handling. With no registered encryption certificate, use the normal creation flow. Otherwise choose reuse of one certificate and supply its versioned Key Vault location, or replace all encryption credentials with one new certificate. Reuse of one among several also removes the others after approval. Enter cancels the certificate decision.
  9. Review the complete plan, including resource names, tenant/subscription, language, service plan, both cache settings, automatic package verification/build, provider settings, scoped roles, the exact encryption key IDs to keep/remove, certificate location or staging name, and application configuration. Bicep receives these exact names; it does not independently calculate a different naming scheme. The plan also lists the six required Azure resource providers and their registration states. These Azure service registrations are separate from your phone-provider selection.
  10. Type Yes once to deploy. No or Enter cancels without Azure changes. Invalid answers prompt again; individual resources do not request additional approvals.

Encryption certificate selection

Setup reads keyCredentials before deployment and identifies type=AsymmetricX509Cert, usage=Encrypt entries, including expired entries. Encrypt is the usage, not the type. It displays each key ID, certificate thumbprint, name, and validity. Signing/verification certificates are not encryption candidates, and unsupported encryption credential types stop setup.

Existing encryption credentials Choice Result after successful deployment
None Creation is automatic; optional -CertificateAction Create. Register one certificate from the setup vault.
One Reuse and provide its Key Vault certificate version, or Replace. Keep the existing key ID, or replace it with one new credential.
Several Reuse, select the key ID to keep, and provide its certificate version; or Replace. Remove only the other encryption entries, or replace all encryption entries with one new credential.

Interactive selection never defaults to the newest certificate or to cleanup. Enter/Cancel stops before deployment; invalid locations can be corrected or followed by another choice. Selecting an action does not apply it: the complete deployment approval lists the exact removals.

For reuse, open Azure portal > Key Vault > Certificates > certificate > version and copy Certificate Identifier, of the form https://<vault>.vault.azure.net/certificates/<name>/<version>. A version is required. Do not supply a vault-only URL, /keys/ URL, public .cer file, secret value, or a versionless reference. Setup reads the public certificate and resolves the corresponding versioned backing secret; the certificate and secret version identifiers need not be identical.

The certificate must exactly match the selected registered certificate, not just its display name or RSA key. It must use exportable RSA of at least 2048 bits with an enabled, certificate-managed PEM backing secret. Both the certificate and registered credential must be valid now with more than 30 days remaining; any secret validity restrictions must also pass. Existing compatible subjects and display names are preserved. A renewed certificate is not silently appended, even if it reuses the same RSA key.

The Function uses its system-assigned identity for the private-key reference, not the setup operator or the outbound provider identity. For a different vault, the plan grants Key Vault Secrets User at the certificate secret's scope in RBAC mode. An access-policy vault instead receives additive Secrets/Get permission while retaining existing permissions; this older model grants secret read vault-wide, which is disclosed in the plan. External vault network rules and certificate issuance policies are not modified. Provide a working network path from the Function; operator access alone does not establish Function access.

All vaults must be in the customer tenant. The source vault defaults to the deployment subscription; supply -KeyVaultSubscriptionId if it is elsewhere. Provider credentials remain in the setup-created vault selected by KEY_VAULT_URL, even when the encryption certificate is reused from another vault.

Cleanup only removes the approved application encryption credentials. It preserves other application credentials and does not delete Key Vault certificates, keys, or secrets. Replacement prepares a separate phone-provider-encryption-<suffix> certificate first. If a run fails before registration, retrying the same approved replacement state reuses that staged certificate. Setup verifies the Function's secret reference before changing the registration, then reads back exactly one matching encryption credential.

Coordinate maintenance for active applications. Every endpoint sharing the registration is affected, and cached/in-flight requests can still use retired keys. This is not zero-downtime rotation and does not implement overlapping decryption keys. Complete the authorized evaluation before activation or returning the endpoint to service.

Reviewing the plan without deploying

Setup-Epp.ps1 does not implement -DryRun, -WhatIf, or PowerShell ShouldProcess. To review a real plan, run interactively, complete preflight, then answer No or press Enter at Deploy this complete plan? Type Yes or No [No]. Do not supply -ApproveDeployment: that switch authorizes the deployment phase without asking again.

This is a cancellation at the approval gate, not an offline simulation. The existing application registration is still required. Before the plan appears, setup can download tools and packages, request required installation, and sign in through Azure CLI. It also checks permissions and regions, verifies package checksums, builds the C# package when selected, and compiles Bicep. Installation, sign-in, and consent prompts are separate from deployment approval. A preflight error is not a successful preview.

After approving the plan

Setup rechecks the subscription and registers only missing Microsoft.Web, Microsoft.Storage, Microsoft.KeyVault, Microsoft.OperationalInsights, Microsoft.Insights, and Microsoft.ManagedIdentity providers before certificates or deployment. Preflight registers nothing. Existing registrations are reused; Registering is sufficient once the requested region is exposed. Polling/recognized propagation retries are bounded; permission and unsupported-region errors stop setup. Subscription-wide registrations are not undone after later failures. See registration recovery.

Supply known values to shorten the prompts. Replace <provider-id> with your selected guided provider's ID (soprano or telesign); neither is a default or recommendation:

.\Setup-Epp.ps1 `
    -TenantId '<customer-tenant-id>' `
    -SubscriptionId '<subscription-id>' `
    -ApplicationId '<existing-client-id>' `
    -Location westus2 `
    -Language javascript `
    -ServicePlan FC1 `
    -Provider '<provider-id>' `
    -EndpointRegion global `
    -ResourcePrefix contoso

Replace the ID placeholders with quoted GUID strings before execution. The -Channel parameter has been removed; omit it from existing setup commands. For an independent second provider deployment, use a different prefix and a dedicated app. The suffix does not include provider/region, so changing those inputs with the same subscription/app/prefix can reconfigure the existing endpoint. See deployment separation and key constraints.

The plan creates or updates the complete deployment. This includes a dedicated resource group, Linux FC1 or EP1 hosting plan, Function App, storage, private package container, and Key Vault. It also includes Log Analytics, Application Insights, an outbound managed identity, diagnostics, Easy Auth, and scoped role assignments. Storage and package access use managed identity instead of account keys or Azure Storage shared access signatures. Telemetry uses the system identity; the outbound identity is selected explicitly, not through a global AZURE_CLIENT_ID.

The Function starts with public ingress disabled. After Bicep creates the vault and permissions, setup applies the approved certificate action. New certificates are self-signed, exportable RSA-2048 with subject/display name CN=ExternalPhoneProvider. Initial creation uses phone-provider-encryption; replacement uses a suffixed staging name; reuse preserves the selected key ID/display name. Setup pins EPP_DECRYPTION_KEY_PEM to its versioned PEM backing secret, verifies resolution, and registers one encryption entry without changing other credentials. It verifies Easy Auth before enabling ingress. SCM basic authentication stays disabled for both plans. FC1 uses Entra-authenticated One Deploy for every language, with remote build for Python. Its functionAppConfig defines runtime, scaling, and managed-identity deployment storage; setup does not write Premium-only runtime/build or WEBSITE_RUN_FROM_PACKAGE settings. One Deploy handles publication and trigger synchronization. EP1 Python uses the Entra-authenticated SCM remote build: setup validates the built Python payload, stores it in private Blob storage, and switches to managed-identity run-from-package. It never mounts the unbuilt Python source ZIP. EP1 then restarts and synchronizes triggers. Both plans verify that SendOtp is registered before reporting success. On publication/startup failure it disables public ingress again; failure to close ingress is reported explicitly rather than hidden. Key-reference checks use bounded identity/role propagation retries. Failure stops certificate registration, but infrastructure/settings may have changed. Metadata resolution does not replace authorized decryption evaluation before activation.

Find the public certificate and timestamped summary in epp-output beside the script, or -OutputDirectory. Plan/cache fields record configuration, not verified runtime behavior. Setup never downloads, writes, or imports the private key locally: the Function receives it through its managed-identity reference. The certificate supplies its backing secret; there is no separate phone-provider-decryption-key secret.

Noninteractive example

Supply every input, including -ServicePlan, and both -NonInteractive -ApproveDeployment to authorize the complete reviewed plan. -NonInteractive alone approves nothing; there is no -Stage, -Resume, -ConfigPath, or policy-approval switch.

Replace the IDs/provider placeholder and choose approved route, region, prefix, and plan. This JavaScript example supports both channels. Establish the CLI user session for ARM and Graph first; MFA/consent may still be required. There is no headless service-principal path. Preinstall Bicep or explicitly allow its installation. In Cloud Shell, reuse the intended directory's login instead of the local az login below; token checks select the subscription.

$tenantId = '<customer-tenant-guid>'
$subscriptionId = '<subscription-guid>'
$applicationId = '<dedicated-app-client-guid>'

az login --tenant $tenantId
if ($LASTEXITCODE -ne 0) { throw 'Azure sign-in failed.' }
az account get-access-token --subscription $subscriptionId --output none
if ($LASTEXITCODE -ne 0) { throw 'Azure subscription token acquisition failed.' }
az account get-access-token --subscription $subscriptionId --resource-type ms-graph --output none
if ($LASTEXITCODE -ne 0) { throw 'Microsoft Graph token acquisition failed.' }

.\Setup-Epp.ps1 `
    -TenantId $tenantId `
    -SubscriptionId $subscriptionId `
    -ApplicationId $applicationId `
    -Location westus2 `
    -Provider '<provider-id>' `
    -EndpointRegion global `
    -Language javascript `
    -ServicePlan FC1 `
    -ResourcePrefix contoso `
    -OutputDirectory .\epp-output `
    -NonInteractive `
    -ApproveDeployment

Use -InstallPrerequisites only when authorizing automatic missing-dependency installation. It installs the Azure CLI Bicep component only; the Microsoft Graph PowerShell SDK is not used. Use the source/version options 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.

Add -PassThru when automation also needs the structured deployment result. The timestamped summary JSON is saved regardless.

The example assumes no encryption credentials. Otherwise choose Reuse or Replace; approval alone never selects or replaces a certificate. Reuse requires both key ID and exact certificate version, even with one credential. Add @reuseCertificate using this reviewed set:

$reuseCertificate = @{
    CertificateAction = 'Reuse'
    EncryptionKeyId = '<key-id-to-keep-from-setup-or-the-app-manifest>'
    KeyVaultCertificateId = 'https://<vault>.vault.azure.net/certificates/<name>/<version>'
}

If necessary, also add KeyVaultSubscriptionId to that hashtable. For deliberate replacement, use -CertificateAction Replace instead of the reuse parameters. This replaces all registered encryption credentials, not just an expired one. Do not blindly rerun a replacement command after a partial failure: inspect the current registration, and choose reuse if the new credential was already registered.

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, endpointRegion, providerTenantId Confirm provider routing, separate from the customer's directory and Azure location. Both SMS and voice use this configuration.
allowedProviderTenantIds Review the complete preserved provider-tenant allowlist, including the selected provider. This does not enable runtime fallback between providers.
providerSmsEndpoint, providerVoiceEndpoint Confirm the distinct or identical provider URLs for SMS and voice. These are outbound provider URLs, not the Function's endpointUrl.
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.
certificateAction, removedEncryptionKeyIds, certificateVaultSubscriptionId Review whether creation, reuse, or replacement occurred, the exact retired application key IDs, and the source certificate's subscription.
certificateReferenceResolved true means the Function's exact secret reference resolved before registration. It is not proof of successful authorized decryption or provider delivery.
endpointServicePrincipalId, microsoftPhoneProviderServicePrincipalId Tenant-local enterprise-application Object IDs; neither is the endpoint app client ID.
source, sourceRevision, packageReleaseTag, packageUrl, sourcePackageSha256, packageSha256, language, servicePlan Commit-pinned setup source, package release 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.

The provider field must match your selected provider. For example, a record for the Global route can contain "endpointRegion": "global", "certificateRenewal": "manual", and "policyChanged": false. Those fields do not prove credentials, delivery, or monitoring work. Follow provider authentication next, even if the script's completion text does not prompt for your provider.

Encryption certificate lifecycle

Certificates issued by setup use 12-month validity, key reuse, and manual renewal. EmailContacts runs 30 days before expiry, not AutoRenew, and needs separately configured vault contacts. Setup creates no contacts and guarantees no notifications.

In Key Vault > Certificates > Certificate contacts, add the approved operations/renewal contact and verify the address and ownership. Portal labels can vary; follow certificate renewal and contacts. 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.

Use Reuse for a rerun with the exact registered certificate and its versioned Key Vault location. Reuse never issues a certificate or adds a new credential. If there are older or duplicate encryption entries, approve which key ID to keep and which entries to retire.

For the Create/Replace paths, only a certificate-not-found response triggers issuance. Azure CLI waits for self-signed issuance. Other errors, including pending-operation conflicts, stop setup rather than overwriting a disabled, incompatible, or near-expiry staged certificate. Keep more than 30 days of validity remaining. No Key Vault version is deleted as part of application cleanup.

Coordinate renewal with the endpoint owner before expiry. Replace stages a new certificate and switches the single-key endpoint under approved maintenance. Alternatively, synchronize the renewed certificate in Microsoft Entra ID and Key Vault through the supported administrator procedure, then Reuse that exact registered version. Setup does not append a second encryption credential, even for the same RSA key.

Vault renewal alone updates neither the app registration nor the pinned Function reference. Reuse leaves an external vault's issuance policy unchanged; automatic issuance there still requires manual endpoint synchronization (certificateRenewal: manual). Account for every endpoint sharing the key and cached/in-flight requests; decryption uses one in-process private key.

Legacy deployments require an explicit decision. A compatible matching Key Vault certificate can be reused even if it has an older/custom subject. A standalone PEM secret without a Key Vault certificate is not a reuse input: arrange an approved secure certificate import with a PEM backing secret, or choose replacement. Setup never exports the private key locally. Do not manually delete registered credentials merely to bypass the selection or matching checks.

Setup rechecks the application credentials against the reviewed plan before changing them. Detected credential changes stop the run and require a new review; this is not an atomic lock. Do not run setup concurrently against the same application. A failure after approval may leave provisioned resources or changed app settings even when no encryption credentials were removed; follow safe recovery.

Source versioning

Defaults are -SourceRepository Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample and -SourceRef main. Setup resolves one commit for supporting PowerShell, Bicep, both catalogs, and all catalog-listed profiles. The package catalog names assets from that repository's latest stable epp-packages-* release; SHA256SUMS.txt verifies them. The plan/summary records URLs and hashes.

For repeatable deployment, pin a reviewed full SHA and -PackageReleaseTag epp-packages-<run>-<attempt>. To test custom source, publish a public fork and matching stable release; align both source options with the downloaded launcher. Local unpushed changes are not downloadable.

Trust the selected repository: its PowerShell executes locally. Provider JSON selects data, not another executable script. Downloads fail closed and temporary files are removed on success/failure.

Offline setup checks

From the repository root, compile the infrastructure and run all setup regression suites without Azure sign-in or resource changes:

$templatePath = Join-Path ([IO.Path]::GetTempPath()) "epp-main-$([Guid]::NewGuid().ToString('N')).json"
az bicep build --file .\setup\infra\main.bicep --outfile $templatePath
if ($LASTEXITCODE -ne 0) { throw 'Bicep compilation failed.' }

$tests = @(
    'Launcher.Tests.ps1',
    'Accounts.Tests.ps1',
    'Graph.Tests.ps1',
    'ProviderConfiguration.Tests.ps1',
    'Selections.Tests.ps1',
    'Certificates.Tests.ps1',
    'Providers.Tests.ps1'
)
foreach ($test in $tests) {
    pwsh -NoProfile -File (Join-Path '.\setup\tests' $test)
    if ($LASTEXITCODE -ne 0) { throw "$test failed." }
}

pwsh -NoProfile -File .\setup\tests\ServicePlans.Tests.ps1 `
    -TemplatePath $templatePath
if ($LASTEXITCODE -ne 0) { throw 'ServicePlans.Tests.ps1 failed.' }
Remove-Item -LiteralPath $templatePath

The suites cover launcher downloads and cleanup; account, subscription, application, and region selection; Azure CLI Graph calls; provider discovery; allowlist preservation; and stale-plan checks. They also cover certificate choices, provider and package selection, and FC1/EP1 deployment. GitHub, Azure, Microsoft Graph, certificate, and publication calls are mocked. CI runs the same eight suites on Windows and Linux. They do not certify live deployments, RBAC propagation, Key Vault issuance, Entra behavior, or provider delivery.

Step 3 - manually validate and activate policy

First complete provider authentication and the deployed acceptance checks. 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.

SMS and voice can share the same endpoint URL and application client ID. Setup's handoff lists both policy targets; validate and activate only the channels you intend to enable, separately.

Activation is an assisted, product-specific gate, not an executable public Graph recipe. The public beta SMS and Voice 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 gets Microsoft's current approved EPP procedure for the selected tenant and channel. It must identify the tool, API and version, exact field placement, required consent and roles, readback steps, validation, and rollback. Delegated Policy.ReadWrite.AuthenticationMethod is a policy-management permission, not proof of EPP entitlement; confirm the approved procedure's permission requirements separately from setup. If this information or the feature is unavailable, leave policy unchanged and record the blocker.

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.