Skip to main content
emnode
Compliance

Harden Azure Function apps

One capability across every Function app: make sure the HTTP surface it exposes is not reachable by anyone the internet, that nobody can attach a debugger or call an admin endpoint, and that every endpoint behind it is authenticated, used and deliberate.

14 min·10 sections·AZURE

Last reviewed

Hardening Function apps: the basics

What does an exposed Azure Function app actually look like?

A Function app is a small, internet-facing web server that runs your code on an HTTP trigger, and its exposure shows up in a handful of distinct shapes rather than one setting. CORS can be left as a wildcard so any website in the world can call the app from a browser. Remote debugging can be left on so a debugger can attach to the running process. Incoming client certificates can be off so the app cannot insist a caller present one. Authentication can be absent so an HTTP-triggered endpoint answers anonymous requests. And old endpoints that nobody calls any more can sit live, unpatched and forgotten. Each is its own Defender for Cloud recommendation, but the underlying risk is the same: an HTTP surface that more of the internet can reach, call or attach to than you meant.

Microsoft Defender for Cloud turns each of these into its own recommendation, which is why one Functions-heavy subscription can fail several Function app checks at once. 'CORS should not allow every resource to access Function Apps', 'Remote debugging should be turned off for Function App', and 'Authentication should be enabled on API endpoints hosted in Function Apps' close the widest doors, and they pair with the client-certificate and unused-endpoint recommendations to form a single capability. They read as separate problems on the report, but they are one job: shrink the HTTP surface to exactly the callers, endpoints and access paths you actually intend.

Most of this exposure is drift, not intent. A wildcard CORS rule that a developer set to unblock a frontend during testing and never tightened, remote debugging left on after a live troubleshooting session, an app deployed before Easy Auth was the default, an endpoint kept past the feature that needed it. The work is to find every Function app whose HTTP surface is wider than intended, decide which callers and endpoints are legitimate, and lock the rest down.

In this lesson you will learn how an Azure Function app expresses its HTTP attack surface, how to find every over-exposed app in a subscription, and how to lock them down without breaking the callers and endpoints that are legitimate. The Controls this lesson covers section lists every Defender for Cloud recommendation in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.

Fun fact

The function nobody was calling

Serverless functions are easy to stand up and just as easy to forget, which is why 'shadow' and abandoned endpoints are now a named category in API security. An HTTP-triggered function written for a campaign that ended, or a test endpoint that shipped to production, keeps answering requests long after anyone remembers it exists, and it stops receiving the attention that keeps the rest of the codebase patched. Defender for Cloud now catalogues the API endpoints inside your Function apps and flags the ones that have seen no traffic, precisely because the endpoint you forgot is the one an attacker is most likely to find first.

Finding Function app exposure across a subscription

Priya is the security lead at a scale-up preparing for its first SOC 2 audit. Defender for Cloud shows Function app findings spread across a dozen apps in two subscriptions that pre-date the team's current guardrails.

Rather than work the findings one by one, Priya starts by listing which apps still allow any origin via CORS and which still have remote debugging switched on, so the genuinely public APIs can be separated from drift before anything changes.

Start by listing the apps whose CORS allows every origin and whether remote debugging is still on. These are the widest-open doors.

$ az functionapp list --query "[].name" -o tsv | while read app; do rg=$(az functionapp show -n "$app" --query resourceGroup -o tsv); cors=$(az functionapp cors show -n "$app" -g "$rg" --query "allowedOrigins" -o tsv); dbg=$(az functionapp config show -n "$app" -g "$rg" --query "remoteDebuggingEnabled" -o tsv); echo "$app $cors $dbg"; done
Name CORS RemoteDebug
---------------- ----- -----------
ordersapiprod * True
internaljobs https://app.contoso.com False
# ordersapiprod allows every origin AND has a debugger pathway open. Close it first.

An app whose CORS is a wildcard and that still has remote debugging on is the highest-value target in this group. Fix these first.

How Defender for Cloud decides a Function app is exposeddeep dive

Most Function app recommendations resolve to one of a few properties on the site or its config. CORS lives at 'properties.cors.allowedOrigins' on the app's web config: when it contains a single '*' entry, any website can call the app from a browser, which is what the CORS recommendation flags. Remote debugging is 'remoteDebuggingEnabled' on the same config: when it is true, a debugger can attach to the running process, and Azure turns it off automatically after 48 hours precisely because it should never be a steady state. Incoming client certificates are 'clientCertEnabled' on the site: when false, the app cannot require a caller to present a certificate.

Authentication is different in kind. The 'Function apps should have authentication enabled' recommendation checks whether App Service Authentication, often called Easy Auth, is switched on, expressed as 'siteAuthEnabled'. Easy Auth is a middleware layer that runs in front of your code and validates a token before any request reaches a function, so when it is off, every HTTP-triggered endpoint relies on whatever your own code does, or does not, check. The unused-endpoint and per-endpoint authentication recommendations come from Defender for APIs, which catalogues the actual HTTP routes inside each app from observed traffic, then flags endpoints that have seen no calls for 30 days and endpoints reachable without authentication. These are preview, traffic-based recommendations rather than a single config flag, so they need the Defender for APIs plan and a window of observed traffic to populate.

Defender for Cloud evaluates the config-based checks against the Microsoft Cloud Security Benchmark on a periodic cycle, so a fix does not flip the recommendation to Healthy instantly. The control-plane change itself is immediate, but the posture catches up on the next assessment, and the traffic-based API recommendations catch up only after enough observed traffic. This matters when you are gathering audit evidence against a deadline.

What is the impact of leaving a Function app exposed?

The direct impact is unauthorised use of your code. An HTTP-triggered function with no authentication answers anyone who finds its URL, and internet-facing endpoints are found quickly by automated scanners. A wildcard CORS rule lets any website call the app from a victim's browser, carrying that user's context. Remote debugging left on is a pathway to attach to the process running your code. Each of these turns a function from a private utility into a callable, abusable surface, and abuse is not only data leakage: a function that triggers downstream work can run up real spend when it is called at scale by someone who is not you.

The second-order impact is blast radius. Every unauthenticated endpoint and over-permissive CORS rule is attack surface that has to be defended continuously, and a forgotten endpoint is the worst case because nobody is watching it or patching it. Locking Function apps down shrinks that surface to the endpoints you actually operate, behind authentication you control, which are the ones you can monitor, rate-limit and reason about.

On the compliance side, every modern framework, ISO 27001, SOC 2, HIPAA, PCI DSS and the UK and EU data-protection regimes, expects evidence that application endpoints authenticate their callers and that the attack surface is minimised. A passing set of Function app recommendations across every subscription is a cheap and defensible artefact to hand an auditor.

How do you lock Function apps down safely?

Work the capability as one loop rather than chasing individual findings. The order matters: identify the legitimate callers and endpoints before you start closing things, so you do not break a live integration.

1. Inventory every app by exposure

List the apps whose CORS allows every origin, that still have remote debugging on, that do not require a client certificate, and that have authentication switched off. Treat this inventory as the source of truth, not the Defender finding count, because one app can trigger several recommendations at once.

2. Separate legitimate callers and endpoints from drift

Most exposure is unintended. For each app, decide which origins genuinely need cross-origin access and which endpoints are still in use. A wildcard CORS rule replaced by the two domains that actually call the app, and an endpoint inventory checked against Defender for APIs traffic, turns 'open to everything' into 'open to exactly what we run'. Everything else is drift and can be closed.

3. Close the doors, highest impact first

Replace wildcard CORS with the explicit list of origins, turn remote debugging off, set a minimum TLS version of 1.2, enable incoming client certificates where the app expects them, and put App Service Authentication in front of any HTTP-triggered app that should not be anonymous. Remove the endpoints Defender for APIs reports as unused. Prioritise apps that handle data over internal utilities. Each change takes effect immediately on the control plane.

4. Ratchet it shut with Azure Policy

Assign the built-in policies that audit Function apps without authentication, with a CORS wildcard, with remote debugging on, or without client certificates, so any new app that ships in the open state is flagged rather than discovered later. These policies are audit-only, so they surface drift without blocking deployments, which is the right posture for a setting that has legitimate exceptions.

# Close the highest-impact Function app exposure first: wildcard CORS, remote
# debugging, weak TLS, and missing client certificates, app by app.
RG=my-rg
APP=ordersapiprod

# 1. Replace wildcard CORS with the origins that actually call the app.
az functionapp cors remove -g "$RG" -n "$APP" --allowed-origins "*"
az functionapp cors add    -g "$RG" -n "$APP" \
  --allowed-origins https://app.contoso.com https://admin.contoso.com

# 2. Turn off remote debugging and require modern TLS.
az functionapp config set -g "$RG" -n "$APP" \
  --remote-debugging-enabled false \
  --min-tls-version 1.2

# 3. Require incoming client certificates where the app expects them.
az functionapp update -g "$RG" -n "$APP" \
  --set clientCertEnabled=true --set clientCertMode=Required

# 4. Put App Service Authentication (Easy Auth) in front of an HTTP-triggered app
#    so anonymous callers are rejected before they reach your code.
az webapp auth update -g "$RG" -n "$APP" \
  --enabled true \
  --action LoginWithAzureActiveDirectory \
  --aad-client-id "$CLIENT_ID" \
  --aad-token-issuer-url "https://sts.windows.net/$TENANT_ID/"

# 5. Ratchet it shut: audit any future app that ships without authentication.
#    Look the built-in policy up by its exact display name, never hardcode the GUID.
pid=$(az policy definition list \
  --query "[?displayName=='Function apps should have authentication enabled'].name" -o tsv)
az policy assignment create \
  --name audit-funcapp-auth \
  --policy "$pid" \
  --scope "/subscriptions/00000000-0000-0000-0000-000000000000"

Quick quiz

Question 1 of 5

Defender for Cloud shows Function app findings across CORS, remote debugging, client certificates and authentication on several apps. What is the most efficient way to think about them?

You can now treat Azure Function apps as one capability rather than a scatter of findings: inventory which apps are callable, debuggable or anonymous more widely than intended, separate the legitimate callers and endpoints, close the rest highest-impact first, and ratchet the estate shut with Azure Policy audit rules. The Controls this lesson covers section below links every recommendation in this group to its deep page and fix.

Back to the library

Controls this lesson covers

One capability, many Microsoft Defender for Cloud recommendations. This lesson is the shared playbook; each control below keeps its own deep page with the exact check, severity and a copy-and-paste fix.