[AP 7] Writing Your Own, and When Not To

A custom definition is a maintenance commitment with no end date. The test for taking one on, the alias constraint that kills most ideas before they start, and the cases where the answer is not a policy at all.


Writing a policy definition takes an afternoon. Owning one takes as long as the rule is assigned, which is usually longer than the person who wrote it stays. That asymmetry should decide most of these arguments and it rarely gets raised in them.

The test

Two conditions, and both have to hold. Nothing in the catalogue addresses it, checked properly by reading rules rather than skimming display names. And the rule is about something specific to your organisation, which in practice means your naming conventions, your tagging taxonomy, an internal standard nobody outside your company shares, or a combination of conditions that is peculiar to how you have chosen to run your estate.

If the second condition fails, be suspicious. A rule that is genuinely general and genuinely absent from a catalogue of thousands is unusual, and the more likely explanations are that it exists under a name you did not search for, or that it cannot be expressed as a policy at all for a reason you are about to discover the hard way.


The alias constraint, which decides most of it

A policy rule can only inspect a resource property that Azure Policy exposes as an alias. Aliases map Resource Manager properties into the policy language, they are published per resource type and per API version, and the coverage is uneven. If the property you care about has no alias, you cannot write the rule. Not with a clever workaround, not with a different operator. The documented path when an alias does not exist is to raise a support ticket and wait.

This is the single largest killer of custom policy ideas and it should be the first thing checked, before any design conversation, because it converts a two-week initiative into a five-minute answer. The Azure Policy extension for Visual Studio Code will enumerate the aliases available on a resource type, and the SDK will do the same. Check first. I have watched teams design an entire control framework around a property that was never addressable.

Check the alias before the design meeting. It converts a fortnight of governance work into a five-minute answer, and about a third of the time the answer is no.

There is a second-order version of the same problem. An alias may exist but return nothing when the property was never set, and a rule that tests for the wrong value rather than for the absence of the right one will pass every resource where nobody specified anything. That is the most common defect in custom definitions and it produces a control that looks like it is working, reports high compliance, and enforces nothing.


Three decisions that are hard to reverse

Mode. Indexed evaluates only resource types that support tags and location, which excludes resource groups and subscriptions. It is required if the rule is about tags or location and it is wrong if the rule needs to reach everything. Microsoft’s troubleshooting guidance is direct about the pairing, and getting it wrong produces a rule that quietly ignores a slice of the estate rather than one that errors.

Definition location. A definition can only be assigned at or beneath where it lives, and it has to live at a management group or a subscription. Create it on a subscription and you have guaranteed a copy the first time another team needs the same rule. Create it high, at the intermediate root, even when the first assignment is small.

Parameters. You can add one later, provided it carries a default value so existing assignments stay valid. You can never remove one, because an assignment might be supplying it. Design the parameter surface as though it is permanent, because it is, and prefer one parameter that takes a list over three that take strings. The effect should always be a parameter with Disabled among its allowed values, since a definition you cannot switch off without deleting the assignment is a definition that will eventually be deleted in a hurry.


What you are signing up for

The obligations are the same as for a fork and they are worth stating separately because people accept them more readily for a copy than for something they wrote. Somebody owns this definition by name. Somebody re-reads it when the resource provider adds an API version, because a new API version can change what an alias resolves to. Somebody notices when Microsoft finally ships a built-in that does the same job, and retires yours rather than running both. And if it is a remediating definition, somebody maintains its roleDefinitionIds array, which built-ins arrive with and custom definitions do not, and which fails at deployment time rather than at authoring time when it is wrong.

None of that is difficult. All of it is invisible until it is urgent, and the estates where it is genuinely happening are the ones where custom definitions live in a repository with an owner in the metadata rather than in a portal with a creation date.


When the answer is not a policy

Some requests arrive shaped like a policy and are not one, and recognising them early is worth more than any authoring skill.

If the rule needs to know whether something was approved, whether a workload is genuinely production, or whether a team completed a review, it needs a judgement rather than a property. The manual effect exists for exactly this and an attestation is an honest way to record that a person decided. Building a rule that infers intent from a tag value produces a control that fails the moment somebody types the tag in a different case, and it moves the real decision into a place nobody is reviewing.

If the rule is really about who may do something rather than what state is acceptable, it is a role assignment. If it is about protecting one specific object that somebody has decided is precious, a lock is more visible and more removable by its owner. And if the rule only matters at deployment time and the estate deploys through a pipeline you control, a check in the pipeline is faster to write, faster to change, and gives a better error message. Policy is the right answer when the rule must hold regardless of who deploys and regardless of which path they used, and that condition is worth saying out loud before writing anything.


From here

The build sheet that follows writes one custom definition from an empty file, finds its alias first, handles the absent-property trap deliberately, and validates it against a resource built to fail as well as one built to pass.


Azure Policy
‹ Previous: [AP 6.1] Build Sheet: Duplicate a Built-in, Change It, Track Upstream
Next: [AP 7.1] Build Sheet: A Custom Definition From Scratch