Azure Functions are a serverless way to run code. They support different programming languages and can scale quickly. As a developer, it is particularly easy to deploy functions directly from Visual Studio or Visual Studio Code.

Functions are often protected from direct access, usually with an Azure Application Gateway. This allows access to be controlled centrally and OWASP protection to be activated. In the following example, public access is permitted for the function, but only requests from the application gateway’s public IP (51.145.145.224) are allowed through for both endpoints. Although the functions offer different network protection settings for the public endpoint and the SCM (Source Control Manager) endpoint, both should generally be protected. The downside of this protection is that direct deployment, e.g. from Visual Studio or Visual Studio Code, no longer works because the public endpoints are protected.

In order to still enable a WebDeploy or ZipDeploy, the SCM endpoint must be published in addition to the regular endpoint. Both endpoints require different health probes, so retrieval and control cannot go through the same Application Gateway listener. At the SCM endpoint, the probe can check for 401 and at the regular endpoint of the function itself, depending on the configuration, but usually for 200.

❌ Deployment via Visual Studio

Unfortunately, deployment via Visual Studio no longer works, even if the following steps are carried out. In order to be able to start the deployment of the function from the VS, the publish profile can be downloaded from the azure portal, adapted and imported. The publish profile contains the original URL for the function (line 7) and the URL for the SCM (line 4). Both lines must be adjusted to the new URLs.

<publishData>
	<publishProfile profileName="tzuehlkefunc - Zip Deploy"
	                publishMethod="ZipDeploy"
	                publishUrl="func-scm.zuehlke.cloud:443"
	                userName="$tzuehlkefunc"
	                userPWD="bY...tK"
	                destinationAppUrl="https://func.zuehlke.cloud"
	                SQLServerDBConnectionString=""
	                mySQLDBConnectionString=""
	                hostingProviderForumLink=""
	                controlPanelLink="http://windows.azure.com"
	                webSystem="WebSites">
		<databases/>
	</publishProfile>
</publishData>

If you now try to deploy the function, there are error messages that do not indicate a solution:

...
2>HeaderCheckFunctionApp -> C:\_src\HeaderCheckFunctionApp\HeaderCheckFunctionApp\obj\Release\net6.0\PubTmp\Out\
2>Publishing C:\_src\HeaderCheckFunctionApp\HeaderCheckFunctionApp\obj\Release\net6.0\PubTmp\HeaderCheckFunctionApp - 20230205214949524.zip to https://func-scm.zuehlke.cloud/api/zipdeploy...
2>Zip Deployment failed. 
========== ...

✅ Deployment via FTP

Deployment via FTPS, on the other hand, works without any problems, since the FTP endpoint cannot be protected by the network configuration. It is even available when public access is completely disabled. The URL from the Deployment Center and the user under Application Scope must be used for the login.

For the Application Scope User, only the character string starting with the $ symbol is used. The host is the FTPS endpoint, but without the /site/wwwroot path and without the ftps:// scheme. It is important to use FTP as the protocol, since SFTP (in contrast to FTPS) only activates encryption after the connection has been established.

Instead of authentication with the Application Scope credentials, the information under User Scope can also be used.

✅ Deployment via REST

The deployment via REST endpoint works without any problems against the new endpoint:

curl -X POST -i -u \$tzuehlkefunc:bY...tK --data-binary @"test.zip" https://func-scm.zuehlke.cloud/api/zipdeploy
HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Date: Sun, 12 Feb 2023 13:18:54 GMT
Content-Length: 0
Connection: keep-alive
Server: Kestrel
Set-Cookie: ARRAffinity=0cca310e733ec368178715a7c1f8277e35aa98778893ffde310dc81fe9036741;Path=/;HttpOnly;Secure;Domain=tzuehlkefunc.scm.azurewebsites.net
Set-Cookie: ARRAffinitySameSite=0cca310e733ec368178715a7c1f8277e35aa98778893ffde310dc81fe9036741;Path=/;HttpOnly;SameSite=None;Secure;Domain=tzuehlkefunc.scm.azurewebsites.net
SCM-DEPLOYMENT-ID: c8e63a41-ed08-462d-ac7a-0e5c8adcc40b

The target is the new SCM endpoint via the Application Gateway, where the path /api/zipdeploy or /api/publish?type=zip is added like described in the documentation. You can then check whether the deployment was successful:

curl -i -u \$tzuehlkefunc:bY...tK https://func-scm.zuehlke.cloud/api/deployments
TTP/1.1 200 OK
Date: Sun, 12 Feb 2023 13:20:06 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 665
Connection: keep-alive
Server: Kestrel
ETag: "f724f3040ec34aee"
Set-Cookie: ARRAffinity=0cca310e733ec368178715a7c1f8277e35aa98778893ffde310dc81fe9036741;Path=/;HttpOnly;Secure;Domain=tzuehlkefunc.scm.azurewebsites.net
Set-Cookie: ARRAffinitySameSite=0cca310e733ec368178715a7c1f8277e35aa98778893ffde310dc81fe9036741;Path=/;HttpOnly;SameSite=None;Secure;Domain=tzuehlkefunc.scm.azurewebsites.net

[
	{
		"id": "c8e63a41-ed08-462d-ac7a-0e5c8adcc40b",
		"status": 3,
		"status_text": "",
		"author_email": "N/A",
		"author": "N/A",
		"deployer": "Push-Deployer",
		"message": "Created via a push deployment",
		"progress": "",
		"received_time": "2023-02-12T13:18:48.7653501Z",
		"start_time": "2023-02-12T13:18:49.8033055Z",
		"end_time": "2023-02-12T13:18:54.7391312Z",
		"last_success_end_time": null,
		"complete": true,
		"active": false,
		"is_temp": false,
		"is_readonly": true,
		"url": "https://tzuehlkefunc.scm.azurewebsites.net/api/deployments/c8e63a41-ed08-462d-ac7a-0e5c8adcc40b",
		"log_url": "https://tzuehlkefunc.scm.azurewebsites.net/api/deployments/c8e63a41-ed08-462d-ac7a-0e5c8adcc40b/log",
		"site_name": "tzuehlkefunc"
	}
]

✅ Access of SCM Portal/Kudu Portal

The SCM portal, also known as the Kudu portal, cannot be opened via the regular SCM URL because it automatically redirects to the original SCM URL. However, if the call is made with the path /basicauthviaproxy (in this example https://func-scm.zuehlke.cloud/basicauthviaproxy), the login is possible. You must use the credentials (Application Scope) from the FTPS Deployment Center blade again:

However, other special SCM URLs, such as /ZipDeployUI, are not working.