Skip to content

Azure resource tags

Patchy has no Azure scanner source — Azure findings arrive through Wiz (or a future source). What the azure Integration provides is the context enhancer: it looks a finding's Azure resource up in Azure Resource Graph, carries its tags onto the finding, and resolves the owning repository from them. It is the Azure sibling of the Cloud Asset Inventory and AWS resource-tags enhancers, and like every enhancer it keys purely on the finding's cloud resource, not on where the finding came from.

One inventory, one question

The enhancer asks one question — given this ARM resource ID, what are the resource's tags? — and unlike AWS there is no backend to choose: Azure Resource Graph is free, always on, and tenant-wide. The enhancer runs one KQL query (Resources | where id =~ …, case-insensitive because ARM resource IDs are) and reads the tags off the row. Scope is every subscription the enhancer's identity can read — no per-subscription enumeration, no estate-wide prerequisite. resourceTags.managementGroup optionally narrows the query to one management group.

Resource Graph is eventually consistent by minutes, which costs nothing here: a finding accumulates for an hour before enhancement runs.

The ownership tags

The shared vocabulary, spelled as tags:

scm-repository-org:      acme          # the organization
scm-repository-name:     infra-prod    # the repository
scm-repository-provider: github        # optional; defaults to github

Or, where a single value is easier to manage:

scm-repository-url:      https://github.com/acme/infra-prod

Like an AWS tag — and unlike a Google Cloud label — an Azure tag value can carry :// verbatim, though the scheme is still optional, for a vocabulary shared across clouds. The key names are configurable (resourceTags.tags, below) for estates with an existing convention.

Beyond the repository, the resource's tags themselves become finding attributes (tag:<key>, capped at 24, beside azure-subscription, resource-type and location) — visible on the tracking issue and to the investigating agent, whether or not a repository resolved.

Enabling it

apiVersion: patchy.bitwisemedia.uk/v1alpha1
kind: Integration
metadata:
  name: azure
  namespace: patchy
spec:
  provider: azure
  azure:
    resourceTags:
      enabled: true
      # Optional: narrow the scope to one management group
      # (default: every subscription the identity can read).
      # managementGroup: platform-mg
      # Optional: the forge host composed into a resolved URL (default github.com).
      # repositoryHost: github.example.com
      # Optional: override the tag names read off a resource.
      # tags:
      #   org: scm-repository-org
      #   name: scm-repository-name
      #   provider: scm-repository-provider
      #   url: scm-repository-url

The context-controller reads the block per enhancement, so changes apply without a restart. An azure Integration carries no secretRef — like its google-cloud and aws siblings, it holds no key material anywhere. Credentials come from the Azure default chain, read-only (the built-in Reader role on the management group or subscriptions is enough):

  • On AKS: Microsoft Entra Workload ID — annotate the service account with the client id of a federated managed identity, and label the pod so the webhook injects the token:
contextController:
  serviceAccount:
    annotations:
      azure.workload.identity/client-id: 00000000-0000-0000-0000-000000000000
  podLabels:
    azure.workload.identity/use: "true"
  • Anywhere else (GKE included): workload identity federation — a federated credential on a Microsoft Entra app or managed identity trusting the cluster's service-account token issuer, and AZURE_CLIENT_ID + AZURE_TENANT_ID + AZURE_FEDERATED_TOKEN_FILE (a projected service-account token) on the context-controller deployment. The SDK picks them up without patchy configuration.

When no repository resolves

The shared semantics apply: a resource without ownership tags — or one Resource Graph has no record of, because it was deleted or is not indexed yet — is a clean answer; a lookup that failed (throttling, an identity binding still propagating) holds and retries instead.

One wrinkle shared with AWS: a Wiz Defend threat that names no concrete resource falls back to a synthetic subscription-level pseudo-resource. No inventory records those, so the enhancer stands aside rather than looking them up.