Thursday, January 15, 2026

Migrating from the Legacy Log Analytics Agent (MMA) to Azure Monitor Agent

The Microsoft Monitoring Agent (MMA), also known as the Log Analytics Agent, was retired in August 2024. Organizations still running MMA on their virtual machines are operating on an unsupported agent, which means no further security updates, bug fixes, or feature additions.

This post covers the key differences between MMA and the Azure Monitor Agent (AMA), and provides a step-by-step approach to completing the migration.

1. Why the Change Was Made

The legacy MMA was designed before the current Azure Monitor architecture existed. It had several limitations that AMA addresses:

  • MMA required workspace credentials stored on the VM — AMA uses Managed Identity, eliminating credential management
  • MMA used a single, workspace-centric configuration — AMA uses Data Collection Rules (DCRs), which separate configuration from deployment and allow the same rule to apply to many machines
  • MMA could not filter data before ingestion — AMA supports KQL transformations at the DCR level, reducing ingestion costs by filtering unnecessary rows before they reach the workspace
  • MMA had limited Linux support — AMA has full feature parity across Windows and Linux

2. Prerequisites

Before migrating, confirm the following:

  • All target VMs have a System-assigned or User-assigned Managed Identity enabled. Navigate to VM > Identity and confirm the status is On
  • The destination Log Analytics workspace is identified
  • Any solutions currently deployed through MMA (such as Update Management, Change Tracking, or Security Center) have been reviewed. Some solutions have separate migration paths

3. Creating a Data Collection Rule

Data Collection Rules define what data AMA collects and where it sends it. A single DCR can be assigned to multiple VMs.

  1. Navigate to Azure Monitor > Data Collection Rules > + Create
  2. Select the Platform type (Windows, Linux, or Both)
  3. Under Data sources, select Add data source
  4. Choose the data type — for example, Windows Event Logs or Linux Syslog
  5. Configure the specific event channels or facility/severity levels required
  6. Under Destinations, add the target Log Analytics workspace
  7. Select Review + create

Following is a recommended set of data sources for a standard Windows server DCR:

Data SourceConfiguration
Windows Event LogsSystem: Critical, Error, Warning / Security: Audit Failure
Performance CountersCPU, Memory, Disk — 60 second sample rate
Syslog (Linux)daemon, kern, syslog — Warning and above

4. Assigning the DCR to Virtual Machines

  1. Open the newly created DCR
  2. Navigate to Resources > + Add
  3. Select the VMs to assign the rule to
  4. Select Apply

AMA is automatically installed on the VM during DCR assignment if it is not already present. Verify the installation by navigating to VM > Extensions + applications and confirming the AzureMonitorWindowsAgent or AzureMonitorLinuxAgent extension is present and in a Succeeded state.

5. Validating Data Flow and Removing MMA

Before removing MMA, validate that data is flowing correctly through AMA.

Navigate to Log Analytics workspace > Logs and run:

Heartbeat
| where TimeGenerated > ago(1h)
| where Category == "Direct Agent"
| summarize count() by Computer

Confirm that all migrated VMs appear in the results. Once validated, remove the MMA extension from each VM via VM > Extensions + applications, select MicrosoftMonitoringAgent, and select Uninstall.

Summary

The MMA to AMA migration is a prerequisite for maintaining supported, secure monitoring across Azure virtual machines. The transition to Data Collection Rules introduces a more flexible and cost-efficient configuration model. Completing the migration removes a known technical debt item and unlocks access to AMA-only features such as ingestion-time transformations and multi-homing to multiple workspaces.


Saturday, December 27, 2025

Securing Azure API Management with Policies

Azure API Management sits between API consumers and backend services, and policies are the mechanism through which you enforce security controls, shape traffic, and transform messages. A well-composed policy pipeline in APIM produces consistent security posture across all APIs without requiring changes to backend code.

This post covers the four policies that should be in place on any production APIM instance: rate limiting, JWT validation, IP filtering, and response header cleanup.

1. Policy Scopes and Execution Order

Policies in Azure API Management can be applied at four levels:

ScopeApplied to
GlobalEvery API in the instance
ProductAll APIs assigned to a product
APIAll operations within a specific API
OperationA single HTTP operation (e.g., POST /orders)

Policies execute from the outermost scope inward. A global inbound policy runs before an API-level inbound policy, which runs before an operation-level policy. The <base /> element controls where the parent scope's policies execute relative to the current scope. Omitting <base /> prevents parent policies from running — which is occasionally intentional but more often an accidental override that disables security controls silently.

To open the policy editor:

  1. Navigate to API Management > APIs
  2. Select the target API or individual operation
  3. Open the Design tab
  4. Select the pencil icon next to Inbound processingOutbound processing, or Backend

2. Rate Limiting with rate-limit-by-key

The rate-limit-by-key policy restricts how many calls a single consumer can make within a rolling time window. The counter key is a policy expression — typically the subscription key, a JWT claim, or the caller's IP address.

Following is a policy limiting each subscription to 100 calls per 60 seconds:

<inbound>
  <base />
  <rate-limit-by-key
    calls="100"
    renewal-period="60"
    counter-key="@(context.Subscription?.Key ?? string.Empty)"
    increment-condition="@(context.Response.StatusCode >= 200 && context.Response.StatusCode < 300)"
  />
</inbound>

Setting increment-condition to only count successful responses avoids penalising consumers for backend errors outside their control. When the limit is exceeded, APIM returns 429 Too Many Requests. I recommend pairing this with a Retry-After header in the outbound section so clients know when the window resets.

Apply this policy at the Product scope so it covers all APIs within that product, then override at the operation level only for endpoints that legitimately require different limits.

3. JWT Validation

The validate-jwt policy validates a JSON Web Token before the request reaches the backend — checking the signature, expiry, and required claims. For APIs secured with Microsoft Entra ID, this eliminates an entire class of authentication bypass risks that arise when each backend independently validates tokens.

Following is a policy that validates an Entra ID-issued token:

<inbound>
  <base />
  <validate-jwt
    header-name="Authorization"
    failed-validation-httpcode="401"
    failed-validation-error-message="Unauthorised. A valid bearer token is required.">
    <openid-config url="https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration" />
    <required-claims>
      <claim name="aud">
        <value>api://<app-id></value>
      </claim>
    </required-claims>
  </validate-jwt>
</inbound>

The <openid-config> element fetches and caches the provider's signing keys automatically. The <required-claims> block ensures the token was issued specifically for this API's audience, preventing a valid token for one API from being replayed against another. Requests without a valid bearer token receive the configured 401 response and never reach the backend.

4. IP Filtering

The ip-filter policy evaluates the caller's IP address against an allowlist or blocklist. It is most useful for internal or partner-facing APIs that should only accept traffic from known networks — office IP ranges, VPN exit nodes, or peered virtual networks.

Following is an allowlist policy permitting a CIDR range and a specific address:

<inbound>
  <base />
  <ip-filter action="allow">
    <address-range from="10.0.0.0" to="10.0.0.255" />
    <address>203.0.113.42</address>
  </ip-filter>
</inbound>

When API Management is deployed behind an Azure Application Gateway or Azure Front Door, the caller IP seen by APIM is the gateway's address, not the client's original IP. In this scenario, extract the original client IP from the X-Forwarded-For header using a set-variable policy before the ip-filter evaluation.

5. Response Header Cleanup

Backend services frequently return headers that expose internal implementation details — server software versions, framework identifiers, or internal hostnames. These are useful during development but should not reach API consumers in production.

Following is an outbound policy that removes common information-disclosure headers and adds a security response header:

<outbound>
  <base />
  <set-header name="X-Powered-By" exists-action="delete" />
  <set-header name="X-AspNet-Version" exists-action="delete" />
  <set-header name="Server" exists-action="delete" />
  <set-header name="X-Content-Type-Options" exists-action="override">
    <value>nosniff</value>
  </set-header>
  <set-header name="X-Frame-Options" exists-action="override">
    <value>DENY</value>
  </set-header>
</outbound>

Apply this at the Global scope so it covers every API without repeating it per-API. Backend teams can then focus on returning correct data — APIM handles response hygiene uniformly.

Summary

APIM policies provide a central enforcement point for security controls that would otherwise be replicated, inconsistently, across every backend service. Rate limiting at the product scope, JWT validation at the API scope, IP filtering for restricted APIs, and outbound header cleanup at the global scope form a baseline security posture that is both effective and maintainable. Start with these four policies and tighten them at the operation level only where specific endpoints require different behaviour.