diff --git a/skills/cloud/azure-review/SKILL.md b/skills/cloud/azure-review/SKILL.md index ac6d6ac7..46589f40 100644 --- a/skills/cloud/azure-review/SKILL.md +++ b/skills/cloud/azure-review/SKILL.md @@ -13,7 +13,7 @@ phase: [assess, operate] frameworks: [CIS-Azure-v2.1.0] difficulty: intermediate time_estimate: "60-90min" -version: "1.0.0" +version: "1.0.1" author: unitoneai license: MIT allowed-tools: Read, Grep, Glob @@ -88,6 +88,28 @@ For detailed CIS benchmark checklist items with specific Terraform patterns, Bic --- +### Step 10.1: App Service Deployment-Plane Basic Authentication Evidence + +For App Service, Function Apps, and deployment slots, verify the deployment plane separately from runtime authentication. Passing `auth_settings_v2`, `https_only`, TLS, client certificates, HTTP/2, or `ftps_state = "Disabled"` does not prove that Kudu/SCM, WebDeploy, ZipDeploy, Local Git, or FTP publishing credentials are blocked. + +Apply these gates before marking App Service publishing posture as compliant: + +- **AZ-APP-PUBLISH-01 -- Resource inventory:** enumerate every `Microsoft.Web/sites` and `Microsoft.Web/sites/slots` resource, including Linux Web Apps, Windows Web Apps, Function Apps, and deployment slots. +- **AZ-APP-PUBLISH-02 -- SCM policy evidence:** require `Microsoft.Web/sites/basicPublishingCredentialsPolicies/scm` or slot equivalent with `properties.allow = false`; Terraform evidence can include `webdeploy_publish_basic_authentication_enabled = false`. +- **AZ-APP-PUBLISH-03 -- FTP policy evidence:** require `Microsoft.Web/sites/basicPublishingCredentialsPolicies/ftp` or slot equivalent with `properties.allow = false`; Terraform evidence can include `ftp_publish_basic_authentication_enabled = false`. +- **AZ-APP-PUBLISH-04 -- Slot parity:** verify every deployment slot has the same SCM and FTP basic publishing credential policy as production. +- **AZ-APP-PUBLISH-05 -- Non-basic deployment replacement:** record the approved deployment method, such as Microsoft Entra ID, managed identity, federated service principal, OIDC-backed GitHub Actions, or Azure Pipelines service connection. +- **AZ-APP-PUBLISH-06 -- Credential invalidation:** after disabling publishing basic auth, require evidence that publish profiles, user-scope deployment credentials, stored service connections, and automation secrets were rotated, removed, or invalidated where they were previously exposed. +- **AZ-APP-PUBLISH-07 -- Policy and audit coverage:** verify Azure Policy or role controls prevent re-enabling basic publishing credentials, and that `AppServiceAuditLogs` or equivalent telemetry captures FTP/WebDeploy authorization attempts. +- **AZ-APP-PUBLISH-08 -- Unsupported evidence:** do not credit runtime-only controls, comments, ticket due dates, `ftps_state` alone, or successful Entra deployment as proof that SCM/FTP basic publishing credentials are disabled. + +Use this supplemental output table for App Service publishing evidence: + +| App/Slot | SCM Basic Auth | FTP Basic Auth | Evidence Source | Deployment Method | Credential Rotation | Audit/Policy Evidence | Status | +|----------|----------------|----------------|-----------------|-------------------|---------------------|-----------------------|--------| +| `` | Disabled / Enabled / Not Evaluable | Disabled / Enabled / Not Evaluable | Terraform / ARM / Azure CLI / Policy export | Entra / OIDC / Managed Identity / Basic / Unknown | Rotated / Missing / Not Applicable | Present / Missing / Not Evaluable | Pass / Fail / Not Evaluable | + +Treat enabled SCM or FTP basic publishing credentials on production apps or slots as **High** severity when deployment credentials or publish profiles are broadly accessible, and **Medium** when exposure is limited but policy/audit coverage is missing. Use **Not Evaluable** when the app inventory exists but the `basicPublishingCredentialsPolicies` export or equivalent Terraform fields are absent. --- @@ -142,6 +164,12 @@ Produce the final report using the structure defined in the Output Format sectio | 8 | Key Vault | X | Y | Z | nn% | | 9 | App Service | X | Y | Z | nn% | +### Supplemental App Service Publishing Evidence + +| App/Slot | SCM Basic Auth | FTP Basic Auth | Evidence Source | Deployment Method | Credential Rotation | Audit/Policy Evidence | Status | +|----------|----------------|----------------|-----------------|-------------------|---------------------|-----------------------|--------| +| | Disabled / Enabled / Not Evaluable | Disabled / Enabled / Not Evaluable | | | | | Pass / Fail / Not Evaluable | + ### Detailed Findings #### [CIS X.Y.Z] @@ -200,6 +228,7 @@ Produce the final report using the structure defined in the Output Format sectio 4. **NSG rules using service tags.** A rule with `source_address_prefix = "Internet"` is equivalent to `0.0.0.0/0`. Both must be flagged for CIS 6.1 and 6.2. 5. **Key Vault purge protection is irreversible.** CIS 8.5 requires `purge_protection_enabled = true`. Note this cannot be disabled once enabled -- flag this for awareness during remediation. 6. **App Service TLS version on both Linux and Windows.** Check `azurerm_linux_web_app` and `azurerm_windows_web_app` resources separately. +7. **Runtime App Service controls are not publishing-plane controls.** `auth_settings_v2`, `https_only`, client certificates, and `ftps_state = "Disabled"` do not disable SCM/Kudu/WebDeploy/ZipDeploy basic authentication. Require `basicPublishingCredentialsPolicies/scm` and `basicPublishingCredentialsPolicies/ftp` evidence or equivalent Terraform fields. --- @@ -225,10 +254,13 @@ Produce the final report using the structure defined in the Output Format sectio - Azure Storage Security: https://learn.microsoft.com/en-us/azure/storage/common/storage-security-guide - Azure Key Vault Best Practices: https://learn.microsoft.com/en-us/azure/key-vault/general/best-practices - Azure App Service Security: https://learn.microsoft.com/en-us/azure/app-service/overview-security +- Disable basic authentication in Azure App Service deployments: https://learn.microsoft.com/en-us/azure/app-service/configure-basic-auth-disable +- Authentication types by deployment methods in Azure App Service: https://learn.microsoft.com/en-us/azure/app-service/deploy-authentication-types - Terraform AzureRM Provider Documentation: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs --- ## Changelog +- **1.0.1** -- Adds App Service SCM/FTP publishing basic authentication evidence gates, output fields, severity guidance, and validation expectations. - **1.0.0** -- Initial release. Full coverage of CIS Microsoft Azure Foundations Benchmark v2.1.0 sections 1 through 9. diff --git a/skills/cloud/azure-review/benchmark-checklist.md b/skills/cloud/azure-review/benchmark-checklist.md index 41a67846..2a568e73 100644 --- a/skills/cloud/azure-review/benchmark-checklist.md +++ b/skills/cloud/azure-review/benchmark-checklist.md @@ -704,3 +704,107 @@ resource "azurerm_linux_web_app" { } } ``` + +### Supplemental -- App Service Deployment-Plane Basic Authentication + +Review SCM/Kudu/WebDeploy/ZipDeploy and FTP publishing basic authentication separately from runtime App Service settings. `ftps_state = "Disabled"` blocks FTP protocol use, but it is not proof that the FTP or SCM publishing credential policies are disabled across apps and slots. + +Required evidence: + +- App and slot inventory for `Microsoft.Web/sites` and `Microsoft.Web/sites/slots`. +- SCM policy export showing `basicPublishingCredentialsPolicies/scm` with `properties.allow = false`. +- FTP policy export showing `basicPublishingCredentialsPolicies/ftp` with `properties.allow = false`. +- Terraform fields such as `webdeploy_publish_basic_authentication_enabled = false` and `ftp_publish_basic_authentication_enabled = false` where the AzureRM provider manages the app. +- Non-basic deployment method evidence, such as Microsoft Entra ID, OIDC-backed GitHub Actions, managed identity, or an Azure Pipelines service connection. +- Publish-profile and deployment-credential rotation/invalidation evidence if basic auth was previously enabled. +- Azure Policy, custom role, or monitoring evidence preventing or detecting re-enable attempts. +- `AppServiceAuditLogs` or equivalent diagnostic evidence for FTP/WebDeploy authorization attempts. + +Terraform AzureRM example: + +```hcl +resource "azurerm_linux_web_app" "api" { + name = "app-prod-api" + resource_group_name = azurerm_resource_group.prod.name + location = azurerm_resource_group.prod.location + service_plan_id = azurerm_service_plan.prod.id + https_only = true + ftp_publish_basic_authentication_enabled = false + webdeploy_publish_basic_authentication_enabled = false + + site_config { + ftps_state = "Disabled" + minimum_tls_version = "1.2" + } +} +``` + +ARM/AzAPI child resource evidence: + +```hcl +resource "azapi_resource" "api_scm_basic_auth" { + type = "Microsoft.Web/sites/basicPublishingCredentialsPolicies@2022-09-01" + name = "scm" + parent_id = azurerm_linux_web_app.api.id + body = { + properties = { + allow = false + } + } +} + +resource "azapi_resource" "api_ftp_basic_auth" { + type = "Microsoft.Web/sites/basicPublishingCredentialsPolicies@2022-09-01" + name = "ftp" + parent_id = azurerm_linux_web_app.api.id + body = { + properties = { + allow = false + } + } +} +``` + +Slot parity example: + +```hcl +resource "azurerm_linux_web_app_slot" "staging" { + name = "staging" + app_service_id = azurerm_linux_web_app.api.id + ftp_publish_basic_authentication_enabled = false + webdeploy_publish_basic_authentication_enabled = false + + site_config { + ftps_state = "Disabled" + } +} +``` + +Azure CLI evidence commands: + +```bash +az resource show \ + --resource-group \ + --namespace Microsoft.Web \ + --resource-type basicPublishingCredentialsPolicies \ + --parent sites/ \ + --name scm \ + --query properties.allow + +az resource show \ + --resource-group \ + --namespace Microsoft.Web \ + --resource-type basicPublishingCredentialsPolicies \ + --parent sites/ \ + --name ftp \ + --query properties.allow +``` + +Review checks: + +- Fail if either SCM or FTP publishing basic auth is enabled on an in-scope production app, Function App, or deployment slot. +- Fail if the app has runtime authentication enabled but no deployment-plane basic authentication policy evidence. +- Fail if production is disabled but a staging or deployment slot still permits SCM or FTP basic publishing credentials. +- Mark Not Evaluable when the inventory is present but policy export, Terraform field, or equivalent evidence is missing. +- Do not accept comments, planned tickets, or `ftps_state` alone as evidence that basic publishing credentials are disabled. +- Prefer policy-as-code or custom-role evidence that blocks `Microsoft.Web/sites/basicPublishingCredentialsPolicies/write` and `Microsoft.Web/sites/slots/basicPublishingCredentialsPolicies/write` for lower-privileged operators. diff --git a/skills/cloud/azure-review/tests/benign/appservice-publishing-basic-auth-disabled.tf b/skills/cloud/azure-review/tests/benign/appservice-publishing-basic-auth-disabled.tf new file mode 100644 index 00000000..9cd4772c --- /dev/null +++ b/skills/cloud/azure-review/tests/benign/appservice-publishing-basic-auth-disabled.tf @@ -0,0 +1,138 @@ +# Benign: App Service and staging slot disable SCM and FTP publishing basic auth +# while using non-basic deployment and audit/policy evidence. + +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.110" + } + azapi = { + source = "Azure/azapi" + version = "~> 1.13" + } + } +} + +resource "azurerm_resource_group" "prod" { + name = "rg-app-prod" + location = "eastus" +} + +resource "azurerm_service_plan" "prod" { + name = "asp-prod" + resource_group_name = azurerm_resource_group.prod.name + location = azurerm_resource_group.prod.location + os_type = "Linux" + sku_name = "P1v3" +} + +resource "azurerm_linux_web_app" "api" { + name = "app-prod-api" + resource_group_name = azurerm_resource_group.prod.name + location = azurerm_resource_group.prod.location + service_plan_id = azurerm_service_plan.prod.id + https_only = true + ftp_publish_basic_authentication_enabled = false + webdeploy_publish_basic_authentication_enabled = false + + identity { + type = "SystemAssigned" + } + + site_config { + ftps_state = "Disabled" + minimum_tls_version = "1.2" + http2_enabled = true + } + + app_settings = { + DEPLOYMENT_AUTH_MODE = "oidc-federated-service-principal" + PUBLISH_PROFILE_ROTATED_AT = "2026-06-01T00:00:00Z" + } +} + +resource "azapi_resource" "api_scm_basic_auth" { + type = "Microsoft.Web/sites/basicPublishingCredentialsPolicies@2022-09-01" + name = "scm" + parent_id = azurerm_linux_web_app.api.id + + body = { + properties = { + allow = false + } + } +} + +resource "azapi_resource" "api_ftp_basic_auth" { + type = "Microsoft.Web/sites/basicPublishingCredentialsPolicies@2022-09-01" + name = "ftp" + parent_id = azurerm_linux_web_app.api.id + + body = { + properties = { + allow = false + } + } +} + +resource "azurerm_linux_web_app_slot" "staging" { + name = "staging" + app_service_id = azurerm_linux_web_app.api.id + ftp_publish_basic_authentication_enabled = false + webdeploy_publish_basic_authentication_enabled = false + + site_config { + ftps_state = "Disabled" + } +} + +resource "azapi_resource" "slot_scm_basic_auth" { + type = "Microsoft.Web/sites/slots/basicPublishingCredentialsPolicies@2022-09-01" + name = "scm" + parent_id = azurerm_linux_web_app_slot.staging.id + + body = { + properties = { + allow = false + } + } +} + +resource "azapi_resource" "slot_ftp_basic_auth" { + type = "Microsoft.Web/sites/slots/basicPublishingCredentialsPolicies@2022-09-01" + name = "ftp" + parent_id = azurerm_linux_web_app_slot.staging.id + + body = { + properties = { + allow = false + } + } +} + +resource "azurerm_monitor_diagnostic_setting" "appservice_audit" { + name = "send-appservice-audit-logs" + target_resource_id = azurerm_linux_web_app.api.id + log_analytics_workspace_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sec/providers/Microsoft.OperationalInsights/workspaces/law-sec" + + enabled_log { + category = "AppServiceAuditLogs" + } +} + +resource "azurerm_role_definition" "prevent_basic_auth_reenable" { + name = "Prevent App Service Basic Publishing Auth" + scope = "/subscriptions/00000000-0000-0000-0000-000000000000" + description = "Prevents lower-privileged operators from enabling App Service SCM or FTP basic publishing credentials." + + permissions { + actions = ["*"] + not_actions = [ + "Microsoft.Web/sites/basicPublishingCredentialsPolicies/write", + "Microsoft.Web/sites/slots/basicPublishingCredentialsPolicies/write", + ] + } + + assignable_scopes = ["/subscriptions/00000000-0000-0000-0000-000000000000"] +} diff --git a/skills/cloud/azure-review/tests/vulnerable/appservice-runtime-auth-publishing-basic-auth-enabled.tf b/skills/cloud/azure-review/tests/vulnerable/appservice-runtime-auth-publishing-basic-auth-enabled.tf new file mode 100644 index 00000000..f9d801eb --- /dev/null +++ b/skills/cloud/azure-review/tests/vulnerable/appservice-runtime-auth-publishing-basic-auth-enabled.tf @@ -0,0 +1,78 @@ +# Vulnerable: runtime App Service hardening is enabled, but deployment-plane SCM and +# FTP publishing basic auth remain enabled or unproven for production and slot. + +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.110" + } + } +} + +resource "azurerm_resource_group" "prod" { + name = "rg-app-prod" + location = "eastus" +} + +resource "azurerm_service_plan" "prod" { + name = "asp-prod" + resource_group_name = azurerm_resource_group.prod.name + location = azurerm_resource_group.prod.location + os_type = "Linux" + sku_name = "P1v3" +} + +resource "azurerm_linux_web_app" "api" { + name = "app-prod-api" + resource_group_name = azurerm_resource_group.prod.name + location = azurerm_resource_group.prod.location + service_plan_id = azurerm_service_plan.prod.id + https_only = true + + # Runtime authentication does not protect Kudu/SCM, WebDeploy, ZipDeploy, + # Local Git, or FTP publishing endpoints. + auth_settings_v2 { + auth_enabled = true + } + + # FTP protocol is disabled, but the publishing credential policies are still + # enabled. This must fail AZ-APP-PUBLISH-02 and AZ-APP-PUBLISH-03. + ftp_publish_basic_authentication_enabled = true + webdeploy_publish_basic_authentication_enabled = true + + site_config { + ftps_state = "Disabled" + minimum_tls_version = "1.2" + http2_enabled = true + } + + app_settings = { + DEPLOYMENT_AUTH_MODE = "publish-profile-basic-auth" + PUBLISH_PROFILE_ROTATION = "planned-next-quarter" + APPSERVICE_AUDIT_LOG_STATUS = "not-enabled" + } +} + +resource "azurerm_linux_web_app_slot" "staging" { + name = "staging" + app_service_id = azurerm_linux_web_app.api.id + + # Slot parity gap: staging still accepts SCM publishing basic auth and has no + # child basicPublishingCredentialsPolicies export proving both policies off. + webdeploy_publish_basic_authentication_enabled = true + + site_config { + ftps_state = "Disabled" + } +} + +resource "azurerm_source_control_token" "legacy_github" { + type = "GitHub" + token = var.legacy_publish_profile_token +} + +variable "legacy_publish_profile_token" { + type = string + sensitive = true +}