.NET · .net-core · API-Management-Service · APIM · Architecture · Azure · Azure Container Registry · AzureContainerApps · AzureDevOps · Bicep · Bicep · C# · IAC · Infrastructure As Code · managed-identity

Secure API with API Management, network integrated Container Apps

Introduction

In today’s digital landscape, APIs play a crucial role in connecting applications and enabling seamless interactions. However, with the increasing importance of APIs, ensuring their security becomes paramount. In this blog post, we’ll explore how to create a secure API using .net and hosted on Azure Container Apps, expose them via Azure API Management with centralized authorization policies, and restrict traffic flow within a virtual network. I’ve also provided a GitHub repository with Bicep files for easy reference.

Azure API Management and Azure Container Apps

Azure API Management is a comprehensive platform that allows organizations to manage, secure, and publish APIs at scale. It acts as a gateway, enabling API developers to monitor, analyze, and control the API traffic. On the other hand, Azure Container Apps provide an efficient way to deploy and manage containerized applications in the cloud.

These two powerful Azure services can be network integrated, allowing you to secure your APIs effectively. Leveraging this integration, you can implement centralized policies and control access to the APIs from a virtual network.

Infrastructure as Code (IaC)

Infrastructure as Code (IaC) is a fundamental approach to provisioning and managing infrastructure in a declarative manner. By using code to define your infrastructure, you can easily version, share, and reproduce it reliably. In this blog post, we promote the use of IaC to establish a consistent and predictable deployment process.

To make things even easier, I have prepared end-to-end Bicep files with modular constructs. Bicep is a domain-specific language (DSL) that simplifies the authoring of Azure Resource Manager templates. Our GitHub repository contains these Bicep files, enabling you to deploy the entire API stack effortlessly.

The modules for the entire stack can be found here, the entry point is main.bicep – which references the all the required azure resources in it.

module virtualNetwork 'modules/virtual-network.bicep' = {
  name: vnetName
  params: {
    vnetName: vnetName
    location: location    
  }
}

module uami 'modules/identity.bicep' = {
  name: uamiName
  params: {
    uamiName: uamiName
    location: location
  }
}

module containerRegistry  'modules/registry.bicep' = {
  name: containerRegistryName
  params: {
    location: location
    registryName: containerRegistryName
    skuName: 'Basic'
    userAssignedIdentityPrincipalId: uami.outputs.principalId
    adminUserEnabled: false
  }
}

module keyvault 'modules/keyvault.bicep' = {
  name: keyvaultName
  params: {
    keyVaultName: keyvaultName
    objectId: uami.outputs.principalId
    enabledForDeployment: false
    enabledForDiskEncryption: false
    enabledForTemplateDeployment: false
    keysPermissions: [
      'get'
      'list'
    ]
    secretsPermissions: [
      'get'
      'list'
    ]
    location: location
    skuName: 'standard'  
  }
}

module logAnalytics 'modules/log-analytics.bicep' = {
  name: logAnalyticsName
  params: {
    logAnalyticsName: logAnalyticsName
    localtion: location
  }
}

module appInsights 'modules/app-insights.bicep' = {
  name: appInsightName
  params: {
    appInsightName: appInsightName
    location: location
    laWorkspaceId: logAnalytics.outputs.laWorkspaceId
  }
}

module acaEnvironment 'modules/environment.bicep' = {
  name: acaEnvName
  params: {
    appInsightKey: appInsights.outputs.InstrumentationKey
    infrastructureSubnetId: virtualNetwork.outputs.defaultSubnetId
    location: location
    envrionmentName: acaEnvName
    laWorkspaceName: logAnalyticsName
  }
}

module apimService 'modules/apim.bicep' = {
  name: apimServiceName
  params: {
    apimServiceName: apimServiceName
    location: location
    sku: sku
    skuCount: skuCount
    publisherEmail: publisherEmail
    publisherName: publisherName
    publicIpAddressName: publicIpAddressName
    subnetName: virtualNetwork.outputs.apimSubnetName
    virtualNetworkName: virtualNetwork.name
  }
}

As we can see from the diagram, the IAC created all the required components and connected them accordingly.

Backend web api

The backend web API in this example is using Azure DevOps REST API as upstream, which requires the web API to accept an access token from end user and uses to invoke the Azure DevOps REST API. However, I wanted to introduce an authorization on the APIM level, so the backend APIs only sees traffics that already has a valid Azure DevOps Authentication Token.

Securing Backend with Centralized Authorization

The policy looks like below:

<policies>
    <inbound>
        <send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="true">
            <set-url>{{azuredevopsendpoint}}</set-url>
            <set-method>GET</set-method>
            <set-header name="Authorization" exists-action="override">
                <value>@(context.Request.Headers.GetValueOrDefault("Authorization","scheme param"))</value>
            </set-header>
            <set-header name="Content-Type" exists-action="override">
                <value>application/json</value>
            </set-header>
        </send-request>
        <choose>
            <when condition="@(((IResponse)context.Variables["tokenstate"]).StatusCode != 200)">
                <return-response>
                    <set-status code="401" reason="Unauthorized" />
                    <set-header name="WWW-Authenticate" exists-action="override">
                        <value>Bearer error="invalid_token"</value>
                    </set-header>
                    <set-header name="Content-Type" exists-action="override">
                        <value>application/json</value>
                    </set-header>
                </return-response>
            </when>
        </choose>
        <set-header name="Host" exists-action="override">
            <value>{{containerappbackendhostname}}</value>
        </set-header>        
    </inbound>
  <backend>
    <forward-request />
  </backend>
  <outbound />
  <on-error />
</policies>

Basically, we do a call to Azure DevOps (to connectionData endpoint) from APIM Policy with the given token- if we get a valid response that proves that the token is valid. Only then we forward the request to the upstream/Backend.

The policy uses some name value pair to avoid hard-coded constants, but they are all taken care of within the Bicep modules.

By using a centralized policy, we can validate access tokens centrally within the API Management layer. This means individual APIs don’t need to handle authorization independently. The API Management gateway takes care of validating access tokens and propagating them to the backend as needed. This centralized approach simplifies the API development process and ensures consistent security measures across the board.

Monitoring

The solution is also integrated with Log Analytics and Applicaiton Insights (including the API logs to the App Insights), which allows end-2-end trackability.

Conclusion

In this blog post, we’ve explored how to build a secure API architecture using Azure Container Apps, Azure API Management, and network integration. By leveraging the power of Infrastructure as Code, I’ve provided end-to-end Bicep files for easy provisioning and management of the entire stack.

Centralized authorization policies in Azure API Management enable a streamlined approach to access control, ensuring that all APIs are uniformly protected. By restricting traffic to the backend within a virtual network, we add an additional layer of security to our API infrastructure.

I invite you to explore the code available in our GitHub repository to get started on creating your own secure and robust API solution. Remember, security is an ongoing process, and as you continue to evolve your APIs, always stay updated with the latest best practices and security measures. Happy coding!

Leave a comment