Calling WebApi from Azure function securely - azure

I have a scenario where I have asp.net core web api and a azure function. I call web api from azure time triggered function for every 1 hour. I do not have authentication enabled on the web api and I do not want public to access it. Only my azure function should be able to access the web api. How can I restrict web api to access from public but only from azure function with out implementing authentication.
I tried the below,
In webapi appsettings file, I updated "AllowedHosts":"https://testfuntionapp.azurewebsites.net". My testfuntionapp is unable to access web api with this change.
I am trying for a cost effective solution.

Please check if my findings help to:
How can I restrict web api to access from public but only from azure function without implementing authentication.
This is where the Azure Virtual Networks comes to.
Create the Virtual Network, configure the Azure Function to only be callable on this VNet and also can configure your core app access to the VNet.
By using Private Endpoints, resources are accessible only via your Virtual Network.
If Virtual Network Integration enabled, then the Azure Function is able to access the designated resource via the configured private endpoints, which is a higher level of security.
References:
Michael S Collier's article on Azure Functions with Private Endpoints.
Access an App Service integrated with a Virtual Network from other Azure resources like Azure Functions

I would also recommend private endpoints as suggested by HariKrishnaRajoli.
In theory it would also suffice to just configure http header filtering for access restriction rules and only allow requests containing a secret header known only to your azure function code. This is probably comparable to "basic" HTTP authentication security, and weaker than the other options.
As an alternative, you can use AAD authentication without really "implementing" much authentication code.
Configure Easy Auth on the WebApi
Configure ManagedIdentity for your Azure Function
Give your the Managed Identity access to call the WebApi using "App Role Assignments"
Extend your Azure Function to acquire and pass access token when calling the WebApi

If you only want your Azure function to be able to access your web API, you can restrict access to your web API by IP address.
In your web API's appsettings.json file, add the following:
"AllowedHosts": "https://testfuntionapp.azurewebsites.net"
This will restrict access to your web API to only requests coming from the specified Azure function.

The fastest and "dirtiest" way to do this, is to pass an API key as a header to your REST API from the function.
You can hardcode the API key to your config file or load it from your DB.
My implementation:
namespace CoreApi.Middleware
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class RequirePartnerApiKeyAttribute : Attribute, IAsyncActionFilter
{
private const string ApiKeyHeaderName = "x-partner-apikey";
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
//----------------------------------------------------
// Validate Api Key
//----------------------------------------------------
if (!context.HttpContext.Request.Headers.TryGetValue(ApiKeyHeaderName, out var apiKey) ||
string.IsNullOrEmpty(apiKey))
{
context.Result = new UnauthorizedResult();
return;
}
var partnerService = context.HttpContext.RequestServices.GetRequiredService<IPartnerService>();
var apiRequest = new PartnerAuthenticateRequest { PartnerApiKey = apiKey };
var partner = partnerService.Authenticate(apiRequest, new CancellationToken()).Result;
if (partner == null)
{
context.Result = new UnauthorizedResult();
return;
}
else
{
if (Enum.IsDefined(typeof(ApiKeyTypes), partner.PartnerName))
context.HttpContext.Items["APIKeyName"] = Enum.Parse(typeof(ApiKeyTypes), partner.PartnerName);
}
//----------------------------------------------------
await next();
}
}
}
You can access the API key from any middleware by calling: context.HttpContext.Items["APIKeyName"]

Related

azure oauth 2.0 how to protect web api from external organization web api?

I am new to Azure and trying to protect/web api hosted in azure using oauth 2.0.
This web api will be called from other web api/deamon which is in control of other organization.
I am aware of client credential flow, but in this scenario external api is hosted outside azure ad. We have no idea of where it is hosted and how this third external web api/deamon is hosted? How should we do authentication/authorization for our web api, so that any external service can use it?
You know about client credential flow, then you should know that this kind of flow doesn't need a user to sign in to generate access token, but only need an azure ad application with the client secret. This azure ad application can come from your tenant, so it doesn't require the web api/deamon which is in control of other organization to have an application, you can create the app in your tenant then provide it to the external web api. What you need to make sure is that the external is really a daemon application.
Let's assume that the external app that need to call your api which is protected by azure ad is a daemon application, then client credential flow is suitable here.
Code for external api to generate access token
//you can see it when you add api permission
var scopes = new[] { "api://exposed_apis_app_id/.default" };
var tenantId = "your_tenant_name.onmicrosoft.com";
var clientId = "YOUR_CLIENT_ID";
var clientSecret = "YOUR_CLIENT_SECRET";
// using Azure.Identity;
var options = new TokenCredentialOptions{AuthorityHost = AzureAuthorityHosts.AzurePublicCloud};
var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, options);
var tokenRequestContext = new TokenRequestContext(scopes);
var token = clientSecretCredential.GetTokenAsync(tokenRequestContext).Result.Token;
Code for your api to add authentication for azure ad, you still have some more configurations, you can refer to my this answer, some related document: authorize the token with role and jwt token configuration.
[Authorize]
public class HelloController : Controller
{
public IActionResult Index()
{
HttpContext.ValidateAppRole("User.Read");//You set it when adding app role
Student stu = new Student();
stu.age = 18;
return Json(stu) ;
}
}
appsettings:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "2c0xxxxxxx57",
"Domain": "tenantname.onmicrosoft.com", // for instance contoso.onmicrosoft.com. Not used in the ASP.NET core template
"TenantId": "common",
"Audience": "8fxxxx78"
}
startup.cs, don't forget "app.UseAuthentication();app.UseAuthorization();" in Configure method
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
services.AddControllersWithViews();
}
create an azure ad application in your tenant and expose an api with a role.
you can create another azure ad application, add client secret and add the application permission created before in the API permissions blade.
provide the application id and client secret to those external app and let them use these to generate access token, then they can use the access token to call your api.
modify your api to authorize the token if has the correct role.

How can we secure API to serve only whitelisted clients? Communication between Azure function and Web API

I am using the below design to secure communication between Azure Function and Web API
Step 1 - Request token from AD
Step 2 - Use token to request web api
Code to call the API
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var endpoint = Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT");
var identity_header = Environment.GetEnvironmentVariable("IDENTITY_HEADER");
var resource = "4df52c7e-3d6f-4865-a499-cebbb2f79d26"; //how to secure this ID
var requestURL = endpoint + "?resource=" + resource + "&api-version=2019-08-01";
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("X-IDENTITY-HEADER", identity_header);
HttpResponseMessage response = await httpClient.GetAsync(requestURL);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
var access_token = JsonConvert.DeserializeObject<TokenResp>(responseBody).access_token;
var APIURL = "https://frankapp.azurewebsites.net";
HttpClient callAPI = new HttpClient();
callAPI.DefaultRequestHeaders.Add("Authorization","Bearer "+ access_token);
HttpResponseMessage APIResponse = await callAPI.GetAsync(APIURL);
return new OkObjectResult(APIResponse.StatusCode);
}
Question
The solution works as planned, However, I see a security loophole here. That is, any azure function that has the above code or resource id can call this API!!!
How can I solve this security issue? How can I make only listed azure functions to call the API?
There are several solutions to secure the API App, as mentioned in the comment, you could validate the token via the claims, use the access restriction rules, etc.
From your code, it uses the MSI(managed identity) to get the token for the AD App of the API App, then uses the token to call the API. In this case, I recommend you to use User assignment required setting to restrict the access of the API App, after doing the steps below, just the MSI of the function can get the token for the API App, no need to do anything else.
1.Navigate to the AD App of your API App in the Azure Active Directory in the portal -> click the Managed application in local directory -> Properties -> set the User assignment required to Yes.
2.Create a security group in AAD and add the MSI service principal as a member to it, then add the group to the Users and groups, then the MSI will also be able to call the function.(For the user, it can be added directly, MSI is different, you need to use this nested way, or leverage the App role)
After the steps above, just the MSI added to the Users and groups can get the token successfully and call the API, there are two similar issues I have answered, here and here. In the two posts, they want to secure the function app, in your case, it is the same logic.
Security between Azure Function and API app using AAD can be done in multiple ways:
Claims in access token
Whitelist all the IP range where azure function is hosted, deny others.
Users and group policy in AAD as security group.
Put App service and AF in a single VNET (though that restricts multi-region)
Object ID verification
Read more: https://www.tech-findings.com/2020/01/securing-function-app-with-azure-active-directory.html

How to ensure user identity in Azure Function Call? ASP NET Core Web App

I am building an ASP NET Core web application that will perform a delete operation for a user on a remote datastore. Currently, I have a frontend web app that communicates with Azure Functions to delete a user. I have a concern with security. How can I make sure the user is the logged-in user? Currently, in my ASP Net Core Web App, I have the user authenticate with AAD using Microsoft as an Identity Provider. From a client-side, the user is verified and taken care of. However, my app calls the Azure Functions endpoints. Is there an extra level of security that I could add. I only want to give my web app (registered in Azure) the ability to call the endpoints.
There are two main ways to authenticate your users. One is to use the App Services authentication options: https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad. This is by far the easier method.
If you need more control over things than what is available in the service, then you can implement validation within the Function itself as Thiago mentioned. Ben Morris did a fairly detailed post on how to implement your own OAUTH provider inside your Function App with C#: https://www.ben-morris.com/custom-token-authentication-in-azure-functions-using-bindings/ If you can take care of the validation in the Function's DI container, validation inside a specific function is fairly clean as seen in the post:
public class ExampleHttpFunction
{
private readonly IAccessTokenProvider _tokenProvider;
public ExampleHttpFunction(IAccessTokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
[FunctionName("ExampleHttpFunction")]
public IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "example")] HttpRequest req, ILogger log)
{
var result = _tokenProvider.ValidateToken(req);
if (result.Status == AccessTokenStatus.Valid)
{
log.LogInformation($"Request received for {result.Principal.Identity.Name}.");
return new OkResult();
}
else
{
return new UnauthorizedResult();
}
}
}

Is there a way to secure an Azure Function that will only be called from a specific Azure Logic App?

I understand that Azure Functions are potentially open endpoints on the internet if I read Microsoft’s documentation correctly and per conversations with a friend who has some experience working with web development paradigms that Azure Functions leverages. A cursory reading of security forums and stack overflow questions on the topic leads me to understand at least a couple options of securing them namely
Azure Active Directory
Shared Access Signatures (SAS) and
Azure Virtual Networks.
Context/ What does my Azure Function do? It manages a blob container related to an ETL of vendor data from a SFTP source to a SQL Endpoint which this ETL utilizes an intermediary blob container for file transfer and long term cold storage of source data. The Azure Function moves the blobs from one container to an archive container after they have been loaded to the SQL endpoint. Why Azure Function to manage the blob containers?
SSIS lacks ability to perform blob manipulation (i.e copy and delete)
Logic App lacks ability to perform a join (of files loaded to SQL endpoint and file names in blob container)
An example of one of the functions is shown here below:
using System.IO;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using System.Net;
using Microsoft.WindowsAzure.Storage.Blob;
using System.Collections.Generic;
using System.Text;
namespace AFA_ArchiveBlob
{
public static class HttpTrigger_BlobInput
{
[FunctionName("HttpTrigger_BlobInput")]
public static async Task<HttpResponseMessage> Run(
//public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "{name}")] HttpRequest req,
string name,
ILogger log,
[Blob("{name}/blobname",FileAccess.ReadWrite,Connection = "AzureWebJobsStorage")] CloudBlobContainer myCloudBlobContainer
)
{
//Execution Logged.
log.LogInformation($"HttpTrigger_BlobInput - C# HTTP trigger function processed a request.");
//Run the query against the blob to list the contents.
BlobContinuationToken continuationToken = null;
List<IListBlobItem> results = new List<IListBlobItem>();
do
{
var response = await myCloudBlobContainer.ListBlobsSegmentedAsync(continuationToken);
continuationToken = response.ContinuationToken;
results.AddRange(response.Results);
}
while (continuationToken != null);
//Query the names of the blobs. Todo: can this be a single line linq query select instead?
List<string> listBlobNames = new List<string>();
foreach (CloudBlockBlob b in results)
{
listBlobNames.Add(b.Name);
}
//Serialize the list of blob names to json for passing to function caller via return statement
var jsonReturn = JsonConvert.SerializeObject(listBlobNames);
log.LogInformation("Returning the following JSON");
log.LogInformation(jsonReturn);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(jsonReturn, Encoding.UTF8, "application/json")
};
}
}
}
Firstly, even though using keys might be convenient, I see that official documentation advises against using keys to secure function endpoint in production scenarios.
I suggest it would be a better choice to go with Azure Active Directory for security.. as explained here Secure an HTTP endpoint in production
How to Implement
I see two possible approaches:
1. Simple Approach: Check that calling application is your Azure logic app specifically
Enable Azure Active Directory Authentication for your Azure Function App. You can simply use Express settings (with create a new Azure AD app)
Enable Managed Service Identity for your Logic App.
Find out appid for Managed Service Identity associated with your logic app.. go to Azure Portal > Azure Active Directory > Enterprise Applications > All Applications > Relevant Service Principal (Explained in more detail with screenshots in another SO post here)
Authenticate your logic app to Azure function using Managed Service Identity as explained here.. Authenticate with managed identity in logic app.. note that resource being accessed will be your Azure function.
In your function code, now you can check that appid claim in access token should exactly match the appid for logic app (i.e. logic app is the one calling your function).. otherwise you can reject the call with Unauthorized exception.
2. A more declarative Approach: Have an application permission defined for Azure function app and check for this permission/role being present in auth token from client calling your function
This approach is a little more declarative, as you define an application permission that needs to be assigned to any application that can call your Azure function.
Enable Azure Active Directory Authentication for your Azure Function App. You can simply use Express settings (with create a new Azure AD app)
Now go to Azure Active Directory > App Registrations > App registration for your function app > Manifest
Add a new application role.. using json like this:
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"displayName": "Can invoke my function",
"id": "fc803414-3c61-4ebc-a5e5-cd1675c14bbb",
"isEnabled": true,
"description": "Apps that have this role have the ability to invoke my Azure function",
"value": "MyFunctionValidClient"
}]
Enable Managed Service Identity for your Logic App.
Find out appid for Managed Service Identity associated with your logic app.. as already explained in approach 1 above
Assign the app permission to this managed service identity..
New-AzureADServiceAppRoleAssignment -ObjectId <logicappmsi.ObjectId> -PrincipalId <logicappmsi.ObjectId> -Id "fc803414-3c61-4ebc-a5e5-cd1675c14bbb" -ResourceId <yourfunctionaadapp.ObjectId>
Authenticate your logic app to Azure function using Managed Service Identity.. as already explained in approach 1 above
Now, in the auth token received by your function, you can check that the role claims collection must contain a role named "MyFunctionValidClient" otherwise you can reject the call with Unauthorized exception.
In addition to the above steps explained by #Rohit Below step is important:
Go to Host.json of the function.
Default authLevel : "function" should be changed to "authLevel": "anonymous".
This does not mean anyone can access the function as with Log on AD sign-in authentication required sign user however with managed identity in logic app function authenticate with service principle.

Creating Media Service Credentials

I'm trying to set up the Azure Media Service. I created the media service from Azure but I don't know where can obtain the account key.
client = new CloudMediaContext(new MediaServicesCredentials(accountName, accountKey));
I'm following this tutorial: Integrating applications with Azure Active Directory
But after get the secret key in step 4 in "To add application credentials, or permissions to access web APIs" doesn't works.
client = new CloudMediaContext(new MediaServicesCredentials(accountName, accountKey));
Azure Media Services announces support for AAD and deprecation of ACS authentication.
Because Azure Active Directory provides powerful role-based access control features and support for more fine-grained access to resources in your account compared to the ACS token authentication model ("account keys"), we strongly recommend that you update your code and migrate from ACS to AAD-based authentication by June 22, 2018.
Also, a key reason for the rapid migration is the upcoming announced deprecation of the ACS key based authentication system.
User Authentication with AAD in Media Services
A native application would first acquire an access token from Azure Active Directory and then use that access token to make all REST API calls.
The following examples show how a daemon application may use AAD web application credentials to authenticate requests with the REST service.
For a REST API request to succeed, the calling user must be a “Contributor” or “Owner” of the Azure Media Services account it is trying to access.
Users of the .NET client SDK for Media Services must upgrade to the latest version on Nuget (windowsazure.mediaservices version 4.1.0.1 or greater) to use AAD authentication for communicating with REST requests.
You could use the following code to connect to the Media Services account.
class Program
{
// Read values from the App.config file.
private static readonly string _AADTenantDomain =
ConfigurationManager.AppSettings["AMSAADTenantDomain"];
private static readonly string _RESTAPIEndpoint =
ConfigurationManager.AppSettings["AMSRESTAPIEndpoint"];
private static readonly string _AMSClientId =
ConfigurationManager.AppSettings["AMSClientId"];
private static readonly string _AMSClientSecret =
ConfigurationManager.AppSettings["AMSClientSecret"];
private static CloudMediaContext _context = null;
static void Main(string[] args)
{
try
{
AzureAdTokenCredentials tokenCredentials =
new AzureAdTokenCredentials(_AADTenantDomain,
new AzureAdClientSymmetricKey(_AMSClientId, _AMSClientSecret),
AzureEnvironments.AzureCloudEnvironment);
var tokenProvider = new AzureAdTokenProvider(tokenCredentials);
_context = new CloudMediaContext(new Uri(_RESTAPIEndpoint), tokenProvider);
// Add calls to methods defined in this section.
// Make sure to update the file name and path to where you have your media file.
IAsset inputAsset =
UploadFile(#"C:\VideoFiles\BigBuckBunny.mp4", AssetCreationOptions.None);
IAsset encodedAsset =
EncodeToAdaptiveBitrateMP4s(inputAsset, AssetCreationOptions.None);
PublishAssetGetURLs(encodedAsset);
}
catch (Exception exception)
{
// Parse the XML error message in the Media Services response and create a new
// exception with its content.
exception = MediaServicesExceptionParser.Parse(exception);
Console.Error.WriteLine(exception.Message);
}
finally
{
Console.ReadLine();
}
}
NOTE: Applications will also need to update their references to include a new assembly "Microsoft.WindowsAzure.MediaServices.Client.Common.Authentication.dll" and add references to that namespace as well as reference to the "Microsoft.IdentityModel.Clients.ActiveDirectory" assembly to get access to the ITokenProvider interface.
Click API access and choose "
Connect to Azure Media Services API with user authentication".
This includes the API endpoint that you need to call, along with the ClientID, Domain, and Resource.
For more details about how to connect to Media Services with Azure AD, you could refer to this article.

Resources