Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,43 @@

This repo deploys and remediates a custom Azure Policy that configures and enforces Arc-enabled SQL Server extension `LicenseType` to a selected target value (for example `Paid` or `PAYG`).

> **Looking for Windows Server instead?** To activate **Windows Server** Azure benefits (Software Assurance / pay-as-you-go) on Arc machines, use the companion policy: **[Activate Azure Benefits for Windows Arc Machines](https://github.com/Azure/Community-Policy/tree/main/policyDefinitions/Compute/activate-azure-benefits-for-windows-arc-machines)**. This sample is for **Arc-enabled SQL Server** only.

## What Is In This Folder

- `policy/azurepolicy.json`: Custom policy definition (DeployIfNotExists).
- `policy/azurepolicy.portal.json`: Portal paste version of the definition (`properties` contents only) for creating the policy by hand in the Azure portal.
- `scripts/deployment.ps1`: Creates/updates the policy definition and policy assignment.
- `scripts/start-remediation.ps1`: Starts a remediation task for the created assignment.
- `docs/screenshots/`: Visual references.

## Deployment paths at a glance

| Path | Use when | Files |
|---|---|---|
| **Command line / pipeline** | You want scope, assignment, role grant and remediation handled for you. | `policy/azurepolicy.json` + `scripts/deployment.ps1` + `scripts/start-remediation.ps1` |
| **Azure Portal** | You want to create the definition by hand and assign it yourself. | `policy/azurepolicy.portal.json` |

## Choosing the license type by edition (compliance)

`Paid` = **"License with Software Assurance"** and is a **customer attestation** of active SA / a SQL Server subscription. Free editions are **not** SA-eligible and must be `LicenseOnly`. See [Manage licensing and billing of SQL Server enabled by Azure Arc](https://learn.microsoft.com/sql/sql-server/azure-arc/manage-license-billing?view=sql-server-ver17#license-sql-server-instances-by-virtual-cores).

**Rule:** if a host has **any** Standard or Enterprise instance → `Paid`. If a host has **only** free editions → `LicenseOnly`. Use `PAYG` for pay-as-you-go hourly billing of Standard/Enterprise.

| Edition / combination | License type |
|---|---|
| Standard | `Paid` |
| Enterprise | `Paid` |
| Developer | `LicenseOnly` |
| Express | `LicenseOnly` |
| Evaluation | `LicenseOnly` |
| Enterprise + Express | `Paid` |
| Enterprise + Developer | `Paid` |
| Standard + Express | `Paid` |
| Standard + Developer | `Paid` |

> This sample applies **one** `targetLicenseType` per assignment and is **not yet edition-aware**. To stay compliant on a mixed estate, **scope each assignment** to hosts of one category (e.g. an assignment over Standard/Enterprise hosts → `Paid`; another over free-only hosts → `LicenseOnly`). The scripted path (`deployment.ps1`) currently accepts only `Paid`/`PAYG`; the portal file additionally supports `LicenseOnly` and defaults to it (the safe, non-attesting default). Automatic edition/combination detection is tracked in [issue #1492](https://github.com/microsoft/sql-server-samples/issues/1492).

## Prerequisites

- PowerShell with Az modules installed (`Az.Resources`).
Expand Down Expand Up @@ -107,6 +137,15 @@ This will:

> **Note:** `deployment.ps1` automatically grants required roles to the policy assignment managed identity at assignment scope, preventing common `PolicyAuthorizationFailed` errors during DeployIfNotExists deployments.

## Deploy via the Azure Portal (copy & paste)

Prefer the portal? The **Policy definition → Policy rule** box expects the `properties` contents. The repo's `policy/azurepolicy.json` also carries a read-only `policyType` and a top-level `version` that the portal rejects, so `policy/azurepolicy.portal.json` has those removed (nothing else changed, plus `LicenseOnly` added to the allowed license types).

1. **Policy → Definitions → + Policy definition.**
2. Set **Definition location**, **Name** (e.g. *Configure Arc-enabled SQL Server license type*), and **Category** = `Azure Arc`.
3. Open [`policy/azurepolicy.portal.json`](./policy/azurepolicy.portal.json), copy its entire contents, clear the **Policy rule** box and paste it in.
4. **Save**, then **Assign**. On the *Parameters* tab choose your **Target license type** per the edition table above. Because the effect is `DeployIfNotExists`, the assignment needs a **system-assigned managed identity + location**; the portal grants the required roles. Create a **remediation task** to update existing instances.

## Start Remediation

Parameter reference:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
{
"displayName": "Configure Arc-enabled SQL Server license type",
"mode": "Indexed",
"description": "This policy configures the license type for Arc-enabled SQL Server extensions to a specified target value.",
"metadata": {
"category": "Azure Arc"
},
"parameters": {
"effect": {
"type": "String",
"metadata": {
"displayName": "Effect",
"description": "Enable or disable the execution of the policy."
},
"allowedValues": [
"DeployIfNotExists",
"Disabled"
],
"defaultValue": "DeployIfNotExists"
},
"sqlServerExtensionTypes": {
"type": "Array",
"metadata": {
"displayName": "SQL Server extension types",
"description": "Arc-enabled SQL Server extension names to target."
},
"allowedValues": [
"WindowsAgent.SqlServer",
"LinuxAgent.SqlServer"
],
"defaultValue": [
"WindowsAgent.SqlServer",
"LinuxAgent.SqlServer"
]
},
"targetLicenseType": {
"type": "String",
"metadata": {
"displayName": "Target license type",
"description": "LicenseType value to enforce on the Arc-enabled SQL Server extension settings."
},
"allowedValues": [
"Paid",
"PAYG",
"LicenseOnly"
],
"defaultValue": "LicenseOnly"
},
"licenseTypesToOverwrite": {
"type": "Array",
"metadata": {
"displayName": "Current license types to overwrite",
"description": "Select which current LicenseType states are eligible for update. Use 'Unspecified' to include resources where LicenseType is missing."
},
"allowedValues": [
"Unspecified",
"Paid",
"PAYG",
"LicenseOnly"
],
"defaultValue": [
"Unspecified",
"Paid",
"PAYG",
"LicenseOnly"
]
}
},
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.HybridCompute/machines/extensions"
},
{
"anyOf": [
{
"field": "name",
"in": "[parameters('sqlServerExtensionTypes')]"
},
{
"field": "name",
"like": "*Agent.SqlServer"
}
]
},
{
"field": "Microsoft.HybridCompute/machines/extensions/type",
"in": "[parameters('sqlServerExtensionTypes')]"
}
]
},
"then": {
"effect": "[parameters('effect')]",
"details": {
"type": "Microsoft.HybridCompute/machines/extensions",
"roleDefinitionIds": [
"/providers/Microsoft.Authorization/roleDefinitions/7392c568-9289-4bde-aaaa-b7131215889d",
"/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7"
],
"name": "[field('fullName')]",
"existenceCondition": {
"anyOf": [
{
"allOf": [
{
"field": "Microsoft.HybridCompute/machines/extensions/settings",
"ContainsKey": "LicenseType"
},
{
"value": "[length(intersection(field('Microsoft.HybridCompute/machines/extensions/settings'), createObject('LicenseType', parameters('targetLicenseType'))))]",
"equals": 1
},
{
"anyOf": [
{
"value": "[parameters('targetLicenseType')]",
"notEquals": "PAYG"
},
{
"allOf": [
{
"value": "[parameters('targetLicenseType')]",
"equals": "PAYG"
},
{
"field": "Microsoft.HybridCompute/machines/extensions/settings",
"ContainsKey": "ConsentToRecurringPAYG"
}
]
}
]
}
]
},
{
"allOf": [
{
"field": "Microsoft.HybridCompute/machines/extensions/settings",
"notContainsKey": "LicenseType"
},
{
"value": "[contains(parameters('licenseTypesToOverwrite'), 'Unspecified')]",
"equals": false
}
]
},
{
"allOf": [
{
"field": "Microsoft.HybridCompute/machines/extensions/settings",
"ContainsKey": "LicenseType"
},
{
"value": "[length(intersection(field('Microsoft.HybridCompute/machines/extensions/settings'), createObject('LicenseType', 'Paid')))]",
"equals": 1
},
{
"value": "[contains(parameters('licenseTypesToOverwrite'), 'Paid')]",
"equals": false
}
]
},
{
"allOf": [
{
"field": "Microsoft.HybridCompute/machines/extensions/settings",
"ContainsKey": "LicenseType"
},
{
"value": "[length(intersection(field('Microsoft.HybridCompute/machines/extensions/settings'), createObject('LicenseType', 'PAYG')))]",
"equals": 1
},
{
"value": "[contains(parameters('licenseTypesToOverwrite'), 'PAYG')]",
"equals": false
},
{
"field": "Microsoft.HybridCompute/machines/extensions/settings",
"ContainsKey": "ConsentToRecurringPAYG"
}
]
},
{
"allOf": [
{
"field": "Microsoft.HybridCompute/machines/extensions/settings",
"ContainsKey": "LicenseType"
},
{
"value": "[length(intersection(field('Microsoft.HybridCompute/machines/extensions/settings'), createObject('LicenseType', 'LicenseOnly')))]",
"equals": 1
},
{
"value": "[contains(parameters('licenseTypesToOverwrite'), 'LicenseOnly')]",
"equals": false
}
]
}
]
},
"evaluationDelay": "AfterProvisioningSuccess",
"deployment": {
"properties": {
"mode": "incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"extensionName": {
"type": "string",
"metadata": {
"description": "The Resource name of the Arc server extension."
}
},
"vmLocation": {
"type": "string",
"metadata": {
"description": "The location of the Arc server."
}
},
"agentName": {
"type": "string",
"metadata": {
"description": "Name of the agent, i.e. WindowsAgent.SQLServer."
}
},
"existingSettings": {
"type": "object",
"metadata": {
"description": "The existing settings on the extension."
}
},
"targetLicenseType": {
"type": "string",
"metadata": {
"description": "LicenseType value to set on the extension."
}
},
"consentTimestamp": {
"type": "string",
"defaultValue": "[utcNow('yyyy-MM-ddTHH:mm:ssZ')]",
"metadata": {
"description": "UTC timestamp for recurring PAYG consent. Auto-generated at deployment time."
}
}
},
"functions": [],
"variables": {
"vmExtensionPublisher": "Microsoft.AzureData",
"baseSettings": {
"LicenseType": "[parameters('targetLicenseType')]"
},
"paygConsentSettings": {
"LicenseType": "[parameters('targetLicenseType')]",
"ConsentToRecurringPAYG": {
"Consented": true,
"ConsentTimestamp": "[parameters('consentTimestamp')]"
}
},
"licenseSettings": "[if(equals(parameters('targetLicenseType'), 'PAYG'), variables('paygConsentSettings'), variables('baseSettings'))]"
},
"resources": [
{
"name": "[parameters('extensionName')]",
"type": "Microsoft.HybridCompute/machines/extensions",
"location": "[parameters('vmLocation')]",
"apiVersion": "2022-11-10",
"properties": {
"publisher": "[variables('vmExtensionPublisher')]",
"type": "[parameters('agentName')]",
"settings": "[union(parameters('existingSettings'), variables('licenseSettings'))]"
}
}
],
"outputs": {}
},
"parameters": {
"extensionName": {
"value": "[field('fullName')]"
},
"vmLocation": {
"value": "[field('location')]"
},
"agentName": {
"value": "[field('name')]"
},
"existingSettings": {
"value": "[field('Microsoft.HybridCompute/machines/extensions/settings')]"
},
"targetLicenseType": {
"value": "[parameters('targetLicenseType')]"
}
}
}
}
}
}
}
}