-
Notifications
You must be signed in to change notification settings - Fork 198
Gap-5: App Service Operate (B+ → A) — SKU selection, custom domains, networking #1639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
95bb7f9
feat(operate): App Service SKU selection, custom domains, networking …
paulyuk cd8f38b
Update plugin/skills/azure-prepare/references/services/app-service/ne…
apwestgarth 5a6920a
Update plugin/skills/azure-prepare/references/services/app-service/ne…
apwestgarth 5222654
docs(azure-prepare): align App Service VNet routing property usage
Copilot a63a0ff
docs(azure-prepare): clarify pricing basis in app service sku guide
Copilot dcbcc0a
Apply suggestions from code review
apwestgarth 0dc1aed
docs(azure-prepare): capture SSL cert thumbprint in CLI flow
Copilot 6f19797
docs(azure-prepare): add Bicep TLS binding follow-up step
Copilot 67551e6
Update networking.md
apwestgarth 0ddea3a
Apply suggestions from code review
apwestgarth f837c78
Update plugin/skills/azure-prepare/references/services/app-service/ne…
apwestgarth 92bf0a2
Apply suggestions from code review
apwestgarth ae870a4
Updated decision tree
apwestgarth eff24b6
Update skill references for App Service
apwestgarth 1c007db
Added mention of Hybrid Connections support at Basic tier level
apwestgarth 14b7464
Fixed duplication of title in Decision tree
apwestgarth 47eaed1
Fixed duplication of title in Decision tree
apwestgarth 9999dda
Update plugin/skills/azure-prepare/references/services/app-service/ne…
apwestgarth c1d32e5
docs(azure-prepare): align app service decision guidance with SKU matrix
Copilot b0dced3
Apply suggestion from @apwestgarth
apwestgarth File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
plugin/skills/azure-prepare/references/services/app-service/custom-domains.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| # App Service Custom Domains and Managed TLS | ||
|
|
||
| ## Prerequisites | ||
|
apwestgarth marked this conversation as resolved.
|
||
|
|
||
| | Requirement | Details | | ||
| |------------|---------| | ||
| | SKU tier | Basic (B1) or higher | | ||
| | DNS access | Ability to create CNAME, A, and TXT records | | ||
| | Domain ownership | Verified via TXT record | | ||
|
|
||
|
apwestgarth marked this conversation as resolved.
|
||
| ## DNS Configuration | ||
|
|
||
| ### Subdomain (CNAME) | ||
|
|
||
| | Record Type | Name | Value | | ||
| |------------|------|-------| | ||
| | CNAME | `www` | `<app-name>.azurewebsites.net` | | ||
| | TXT | `asuid.www` | `<verification-id>` | | ||
|
|
||
| ### Apex / Root Domain (A Record) | ||
|
|
||
| | Record Type | Name | Value | | ||
| |------------|------|-------| | ||
| | A | `@` | `<app-ip-address>` | | ||
| | TXT | `asuid` | `<verification-id>` | | ||
|
|
||
| Get the verification ID and IP address: | ||
|
|
||
| ```bash | ||
| # Get verification ID | ||
| az webapp show -n $APP -g $RG --query "customDomainVerificationId" -o tsv | ||
|
|
||
| # Get IP address (for A records) | ||
| az webapp show -n $APP -g $RG --query "inboundIpAddress" -o tsv | ||
| ``` | ||
|
|
||
| > 💡 **Tip:** Prefer CNAME records for subdomains. For apex domains, consider using an Azure DNS alias record to avoid hardcoding IP addresses that may change. | ||
|
|
||
| ## Bind Custom Domain via CLI | ||
|
|
||
| ```bash | ||
| # Add custom domain | ||
| az webapp config hostname add -n $APP -g $RG --hostname www.contoso.com | ||
|
|
||
| # Create managed certificate (free) | ||
| az webapp config ssl create -n $APP -g $RG --hostname www.contoso.com | ||
|
|
||
| # Capture certificate thumbprint | ||
| THUMBPRINT=$(az webapp config ssl list -n $APP -g $RG \ | ||
| --query "[?contains(hostNames, 'www.contoso.com')].thumbprint | [0]" -o tsv) | ||
|
|
||
| # Bind the certificate | ||
| az webapp config ssl bind -n $APP -g $RG \ | ||
| --certificate-thumbprint $THUMBPRINT --ssl-type SNI | ||
|
apwestgarth marked this conversation as resolved.
|
||
| ``` | ||
|
|
||
| ## Bicep — Custom Domain with Managed Certificate | ||
|
|
||
| ```bicep | ||
| resource customDomain 'Microsoft.Web/sites/hostNameBindings@2022-09-01' = { | ||
| parent: webApp | ||
| name: 'www.contoso.com' | ||
| properties: { | ||
| siteName: webApp.name | ||
| hostNameType: 'Verified' | ||
| sslState: 'Disabled' // enable after cert is created | ||
| } | ||
|
apwestgarth marked this conversation as resolved.
|
||
| } | ||
|
|
||
| resource managedCert 'Microsoft.Web/certificates@2022-09-01' = { | ||
| name: 'www.contoso.com' | ||
| location: location | ||
| properties: { | ||
| serverFarmId: appServicePlan.id | ||
| canonicalName: 'www.contoso.com' | ||
| } | ||
| dependsOn: [customDomain] | ||
|
apwestgarth marked this conversation as resolved.
|
||
| } | ||
|
apwestgarth marked this conversation as resolved.
|
||
| ``` | ||
|
|
||
| Then run a follow-up Bicep deployment to enable SNI and bind the managed certificate to the hostname: | ||
|
|
||
| ```bicep | ||
| resource managedCert 'Microsoft.Web/certificates@2022-09-01' existing = { | ||
| name: 'www.contoso.com' | ||
| } | ||
|
|
||
| resource customDomainTlsBinding 'Microsoft.Web/sites/hostNameBindings@2022-09-01' = { | ||
| parent: webApp | ||
| name: 'www.contoso.com' | ||
| properties: { | ||
| siteName: webApp.name | ||
| hostNameType: 'Verified' | ||
| sslState: 'SniEnabled' | ||
| thumbprint: managedCert.properties.thumbprint | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| > ⚠️ **Warning:** Managed certificate creation requires the DNS records to be in place first. The hostname binding must exist before requesting the certificate. | ||
|
|
||
| ## Terraform — Custom Domain with Managed Certificate | ||
|
|
||
| ```hcl | ||
| resource "azurerm_app_service_custom_hostname_binding" "domain" { | ||
| hostname = "www.contoso.com" | ||
| app_service_name = azurerm_linux_web_app.app.name | ||
| resource_group_name = azurerm_resource_group.rg.name | ||
| } | ||
|
|
||
| resource "azurerm_app_service_managed_certificate" "cert" { | ||
| custom_hostname_binding_id = azurerm_app_service_custom_hostname_binding.domain.id | ||
| } | ||
|
|
||
| resource "azurerm_app_service_certificate_binding" "binding" { | ||
| hostname_binding_id = azurerm_app_service_custom_hostname_binding.domain.id | ||
| certificate_id = azurerm_app_service_managed_certificate.cert.id | ||
| ssl_state = "SniEnabled" | ||
| } | ||
| ``` | ||
|
|
||
| ## TLS Options | ||
|
|
||
| | Option | Cost | Renewal | Use Case | | ||
| |--------|------|---------|----------| | ||
| | App Service Managed Certificate | Free | Auto-renewed | Standard custom domains | | ||
| | App Service Certificate (purchased) | ~$70/yr | Auto-renewed | Extended validation, wildcard | | ||
| | Bring your own certificate | Varies | Manual | Enterprise PKI, specific CA | | ||
|
|
||
| ### Enforce HTTPS Only | ||
|
|
||
| ```bicep | ||
| resource webApp 'Microsoft.Web/sites@2022-09-01' = { | ||
| name: appName | ||
| location: location | ||
| properties: { | ||
| httpsOnly: true | ||
| // ... | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ```hcl | ||
| resource "azurerm_linux_web_app" "app" { | ||
| name = var.app_name | ||
| # ... | ||
| https_only = true | ||
| } | ||
| ``` | ||
|
|
||
| ## Minimum TLS Version | ||
|
|
||
| ```bash | ||
| # Set minimum TLS version to 1.2 | ||
| az webapp config set -n $APP -g $RG --min-tls-version 1.2 | ||
| ``` | ||
|
|
||
| ```bicep | ||
| siteConfig: { | ||
| minTlsVersion: '1.2' | ||
| } | ||
| ``` | ||
|
|
||
| > ⚠️ **Warning:** TLS 1.0 and 1.1 are deprecated. Always set minimum TLS version to 1.2 for production workloads. | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| | Issue | Cause | Fix | | ||
| |-------|-------|-----| | ||
| | Domain verification fails | Missing TXT record | Add `asuid` TXT record and wait for DNS propagation | | ||
| | Certificate creation fails | DNS not yet propagated | Wait 5-15 min for propagation; verify with `nslookup` | | ||
| | SSL binding error | SKU too low | Upgrade to Basic (B1) or higher | | ||
| | Managed cert not renewing | DNS record changed | Verify CNAME/A record still points to the app | | ||
200 changes: 200 additions & 0 deletions
200
plugin/skills/azure-prepare/references/services/app-service/networking.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| # App Service Networking | ||
|
|
||
| VNet integration, Private Endpoints, Access Restrictions, and Hybrid Connections. | ||
|
apwestgarth marked this conversation as resolved.
|
||
|
|
||
| ## Feature Availability by SKU | ||
|
|
||
| | Feature | Free | Basic | Standard | Premium | Isolated | | ||
| |---------|:-:|:-:|:-:|:-:|:-:| | ||
| | VNet integration (outbound) | ❌ | ✅ | ✅ | ✅ | ✅ (native) | | ||
| | Private Endpoints (inbound) | ❌ | ✅ | ✅ | ✅ | ✅ | | ||
| | Access Restrictions | ✅ | ✅ | ✅ | ✅ | ✅ | | ||
| | Hybrid Connections | ❌ | 5 | 25 | 200 | 200 | | ||
|
apwestgarth marked this conversation as resolved.
|
||
| | Access to service-endpoint-protected resources | ❌ | ✅ | ✅ | ✅ | ✅ | | ||
| > Note: Service endpoints are configured on VNets/subnets and downstream services (e.g., Storage, SQL). App Service accesses them via VNet integration rather than enabling service endpoints directly on the app. | ||
|
|
||
|
apwestgarth marked this conversation as resolved.
|
||
| ## VNet Integration (Outbound) | ||
|
|
||
| Routes outbound traffic from the app through a VNet subnet, enabling access to private resources (databases, storage, VMs). | ||
|
|
||
| ### Subnet Requirements | ||
|
|
||
| | Requirement | Value | | ||
| |------------|-------| | ||
| | Minimum subnet size | `/26` (64 addresses) recommended | | ||
| | Delegation | `Microsoft.Web/serverFarms` | | ||
| | Dedicated | One subnet per App Service plan | | ||
|
|
||
| ### Bicep — VNet Integration | ||
|
|
||
| ```bicep | ||
| resource subnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = { | ||
| parent: vnet | ||
| name: 'app-service-subnet' | ||
| properties: { | ||
| addressPrefix: '10.0.1.0/26' | ||
| delegations: [ | ||
| { | ||
| name: 'Microsoft.Web.serverFarms' | ||
| properties: { serviceName: 'Microsoft.Web/serverFarms' } | ||
| } | ||
| ] | ||
| } | ||
| } | ||
|
|
||
| resource webApp 'Microsoft.Web/sites@2024-11-01' = { | ||
| name: appName | ||
| location: location | ||
| properties: { | ||
| serverFarmId: appServicePlan.id | ||
| virtualNetworkSubnetId: subnet.id | ||
| outboundVnetRouting: { | ||
| allTraffic: true // route all outbound through VNet | ||
| } | ||
| } | ||
|
apwestgarth marked this conversation as resolved.
|
||
| } | ||
| ``` | ||
|
|
||
| ### CLI - VNet Integration | ||
|
|
||
| ```bash | ||
| # Configure virtual network integration | ||
| az webapp vnet-integration add --resource-group RG --name APP --vnet VNET --subnet SUBNET | ||
|
|
||
| # Update app configuration to route all outbound traffic through the virtual network integration | ||
| az resource update --resource-group RG --name APP --resource-type "Microsoft.Web/sites" --set properties.outboundVnetRouting.allTraffic=true | ||
| ``` | ||
|
|
||
|
|
||
| > 💡 **Tip:** Set `outboundVnetRouting.allTraffic: true` to route ALL outbound traffic through the VNet. Without this, only RFC1918 traffic is routed through the VNet. | ||
|
|
||
| ## Private Endpoints (Inbound) | ||
|
|
||
| Expose the app on a private IP address within your VNet. Public access can be disabled entirely. | ||
|
|
||
| ### Bicep — Private Endpoint | ||
|
|
||
| ```bicep | ||
| resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-11-01' = { | ||
| name: '${appName}-pe' | ||
| location: location | ||
| properties: { | ||
| subnet: { id: privateEndpointSubnet.id } | ||
| privateLinkServiceConnections: [ | ||
| { | ||
| name: '${appName}-connection' | ||
| properties: { | ||
| privateLinkServiceId: webApp.id | ||
| groupIds: ['sites'] | ||
|
apwestgarth marked this conversation as resolved.
|
||
| } | ||
| } | ||
| ] | ||
| } | ||
| } | ||
|
apwestgarth marked this conversation as resolved.
|
||
|
|
||
| resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { | ||
| name: 'privatelink.azurewebsites.net' | ||
| location: 'global' | ||
| } | ||
|
|
||
| resource dnsLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { | ||
| parent: privateDnsZone | ||
| name: '${vnet.name}-link' | ||
| location: 'global' | ||
| properties: { | ||
| virtualNetwork: { id: vnet.id } | ||
| registrationEnabled: false | ||
| } | ||
| } | ||
|
apwestgarth marked this conversation as resolved.
apwestgarth marked this conversation as resolved.
|
||
|
|
||
| resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-11-01' = { | ||
| parent: privateEndpoint | ||
| name: 'default' | ||
| properties: { | ||
| privateDnsZoneConfigs: [ | ||
| { | ||
| name: 'webapp-dns-zone' | ||
| properties: { | ||
| privateDnsZoneId: privateDnsZone.id | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
|
jongio marked this conversation as resolved.
|
||
| ### CLI - Private Endpoint | ||
|
|
||
| ```bash | ||
| # Retrieve web app resource id | ||
| id=$(az webapp show --name APP --resource-group RG --query id --output tsv) | ||
|
|
||
| # Create Private Endpoint | ||
| az network private-endpoint create --connection-name CONNECTIONNAME --name private-endpoint --private-connection-resource-id $id --resource-group RG --subnet SUBNET --group-id sites --vnet-name VNET | ||
|
|
||
| # Create Private DNS Zone | ||
| az network private-dns zone create --resource-group RG --name "privatelink.azurewebsites.net" | ||
|
|
||
| # Link the DNS Zone to virtual network | ||
| az network private-dns link vnet create --resource-group RG --zone-name "privatelink.azurewebsites.net" --name dns-link --virtual-network VNET --registration-enabled false | ||
|
|
||
| ``` | ||
|
|
||
| > ⚠️ **Warning:** Private Endpoints require Basic (B1+) or higher tier. The private DNS zone `privatelink.azurewebsites.net` must be linked to the VNet for name resolution. | ||
|
|
||
| ## Access Restrictions | ||
|
|
||
| Control inbound access with IP-based or service-tag rules. Available on all SKUs. | ||
|
|
||
| ### Bicep — Access Restrictions | ||
|
|
||
| ```bicep | ||
| siteConfig: { | ||
| ipSecurityRestrictions: [ | ||
| { | ||
| name: 'allow-office' | ||
| priority: 100 | ||
| action: 'Allow' | ||
| ipAddress: '203.0.113.0/24' | ||
| } | ||
| { | ||
| name: 'deny-all' | ||
| priority: 2147483647 | ||
| action: 'Deny' | ||
| ipAddress: 'Any' | ||
|
apwestgarth marked this conversation as resolved.
|
||
| } | ||
| ] | ||
| scmIpSecurityRestrictionsUseMain: true | ||
| } | ||
| ``` | ||
|
|
||
| ### CLI - Access Restrictions | ||
|
|
||
| ```bash | ||
| # Add restriction to allow traffic from set range used by the office | ||
| az webapp config access-restriction add --resource-group RG --name APP --rule-name 'allow-office' --action Allow --ip-address 203.0.113.0/24 --priority 100 | ||
|
|
||
| # Add restriction to deny access from any other address range | ||
| az webapp config access-restriction add --resource-group RG --name APP --rule-name 'deny-all' --action Deny --ip-address Any --priority 2147483647 | ||
|
|
||
| # Set SCM Site (Kudu) to use same access restrictions as main site | ||
| az webapp config access-restriction set -g RG -n APP --use-same-restrictions-for-scm-site true | ||
| ``` | ||
|
|
||
| > 💡 **Tip:** Always restrict the SCM/Kudu site too. Use `scmIpSecurityRestrictionsUseMain: true` to inherit main site rules, or define separate SCM rules. | ||
|
|
||
| ## Hybrid Connections | ||
|
|
||
| Connect to on-premises resources without VPN. Requires Basic tier or higher. Uses Hybrid Connection Manager (HCM) agent on-premises relaying through Azure Relay. | ||
|
|
||
| > ⚠️ **Warning:** Each Hybrid Connection maps to a single host:port endpoint. Basic tier supports 5; Standard tier supports 25; Premium/Isolated support 200. | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| | Issue | Cause | Fix | | ||
| |-------|-------|-----| | ||
| | Cannot reach private DB | VNet integration not enabled | Enable VNet integration; check `outboundVnetRouting.allTraffic` | | ||
| | DNS resolution fails | Private DNS zone not linked | Link `privatelink.*` DNS zone to VNet | | ||
| | Access restriction not working | Priority ordering wrong | Lower numbers = higher priority; check rule order | | ||
| | Hybrid Connection timeout | HCM not running | Verify HCM service status on-premises | | ||
| | Outbound traffic blocked | NSG rules on subnet | Allow outbound to required services in NSG | | ||
|
jongio marked this conversation as resolved.
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.