|
1 | | -# LicenseType-SQLArc |
| 1 | +# Arc-enabled SQL Server license type configuration with Azure Policy |
2 | 2 |
|
3 | | -This Azure Policy ensures that all SQL Arc servers have the required `LicenseType` value. Servers that do not match the required license type are marked as non-compliant. The remediation task sets `LicenseType` to the value specified by the `requiredLicenseType` parameter. |
| 3 | +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`). |
4 | 4 |
|
5 | | -Use Azure CLI or PowerShell to create the policy definition. |
| 5 | +## What Is In This Repo |
6 | 6 |
|
7 | | -## Artifacts |
| 7 | +- `policy/azurepolicy.json`: Custom policy definition (DeployIfNotExists). |
| 8 | +- `scripts/deployment.ps1`: Creates/updates the policy definition and policy assignment. |
| 9 | +- `scripts/start-remediation.ps1`: Starts a remediation task for the created assignment. |
| 10 | +- `docs/screenshots/`: Visual references. |
8 | 11 |
|
9 | | -- **policy.json**: Main policy definition referencing external parameter and rule files. |
10 | | -- **params.json**: Defines policy parameters. |
11 | | -- **rules.json**: Contains the policy rule logic. |
| 12 | +## Prerequisites |
12 | 13 |
|
13 | | -## Copy policy artifacts to your environment |
| 14 | +- PowerShell with Az modules installed (`Az.Resources`). |
| 15 | +- Logged in to Azure (`Connect-AzAccount`). |
| 16 | +- Permissions to create policy definitions/assignments and remediation tasks at target scope. |
14 | 17 |
|
15 | | -```PowerShell |
| 18 | +## Deploy Policy |
16 | 19 |
|
17 | | -curl https://raw.githubusercontent.com/microsoft/sql-server-samples/refs/heads/master/samples/manage/azure-arc-enabled-sql-server/compliance/arc-sql-license-type-compliance/params.json -o params.json |
18 | | -curl https://raw.githubusercontent.com/microsoft/sql-server-samples/refs/heads/master/samples/manage/azure-arc-enabled-sql-server/compliance/arc-sql-license-type-compliance/rules.json -o rules.json |
| 20 | +Parameter reference: |
19 | 21 |
|
20 | | -``` |
| 22 | +| Parameter | Required | Default | Allowed values | Description | |
| 23 | +|---|---|---|---|---| |
| 24 | +| `ManagementGroupId` | Yes | N/A | Any valid management group ID | Scope where the policy definition is created. | |
| 25 | +| `ExtensionType` | No | `Both` | `Windows`, `Linux`, `Both` | Targets the Arc SQL extension platform. When `Both` (default), a single policy definition and assignment covers both platforms. When a specific type is selected, the naming and scope are tailored to that platform. | |
| 26 | +| `SubscriptionId` | No | Not set | Any valid subscription ID | If provided, policy assignment scope is the subscription. | |
| 27 | +| `TargetLicenseType` | Yes | N/A | `Paid`, `PAYG` | Target `LicenseType` value to enforce. | |
| 28 | +| `LicenseTypesToOverwrite` | No | All | `Unspecified`, `Paid`, `PAYG`, `LicenseOnly` | Select which current license states are eligible for update. Use `Unspecified` to include resources with no `LicenseType` configured. | |
21 | 29 |
|
22 | | -## Create policy |
| 30 | +Definition and assignment creation: |
23 | 31 |
|
24 | | -Use the following command to create policy |
| 32 | +1. Clone the repo. |
25 | 33 |
|
26 | | -```PowerShell |
| 34 | +```powershell |
| 35 | +git clone https://github.com/microsoft/sql-server-samples.git |
| 36 | +cd sql-server-samples/samples/manage/azure-arc-enabled-sql-server/compliance/arc-sql-license-type-compliance |
| 37 | +``` |
27 | 38 |
|
28 | | -$SubId = "<your-subscription-id>" |
29 | | -$PolicyName = "LicenseType-SQLArc" |
| 39 | +2. Login to Azure. |
30 | 40 |
|
31 | | -az policy definition create ` |
32 | | - --name $PolicyName ` |
33 | | - --display-name $PolicyName ` |
34 | | - --description "This Azure Policy ensures that all SQL Arc servers have the required LicenseType. Servers that do not match are marked as non-compliant and remediated to the required license type." ` |
35 | | - --rules "@rules.json" ` |
36 | | - --params "@params.json" ` |
37 | | - --mode Indexed ` |
38 | | - --subscription $SubId ` |
39 | | - --only-show-errors | Out-Null |
| 41 | +```powershell |
| 42 | +Connect-AzAccount |
40 | 43 | ``` |
41 | 44 |
|
42 | | -## Assign policy |
| 45 | +```powershell |
| 46 | +# Example: target both platforms (default) |
| 47 | +.\scripts\deployment.ps1 -ManagementGroupId "<management-group-id>" -SubscriptionId "<subscription-id>" -TargetLicenseType "PAYG" -LicenseTypesToOverwrite @("Paid") |
43 | 48 |
|
44 | | -Use the following command to assign policy |
| 49 | +# Example: target only Linux |
| 50 | +.\scripts\deployment.ps1 -ManagementGroupId "<management-group-id>" -ExtensionType "Linux" -SubscriptionId "<subscription-id>" -TargetLicenseType "PAYG" -LicenseTypesToOverwrite @("Paid") |
| 51 | +``` |
| 52 | +The first example (without `-ExtensionType`) will: |
| 53 | +* Create/update a single policy definition and assignment covering **both** Windows and Linux. |
| 54 | +* Assign that policy at the specified subscription scope. |
| 55 | +* Enforce LicenseType = PAYG. |
| 56 | +* Update only resources where current `LicenseType` is `Paid`. |
45 | 57 |
|
46 | | -```PowerShell |
| 58 | +The second example creates a Linux-specific definition and assignment, with platform-tailored naming. |
47 | 59 |
|
48 | | -$SubId = "<your-subscription-id>" |
49 | | -$RgName = "<your-resource-group>" # optional; set to "" to target subscription scope |
50 | | -$Location = "<your-azure-region>" # e.g., eastus, westus2 |
51 | | -$RequiredLicenseType = "PAYG" # e.g., PAYG, LicenseOnly |
| 60 | +Scenario examples: |
52 | 61 |
|
53 | | -if ([string]::IsNullOrWhiteSpace($RgName)) { |
54 | | - $Scope = "/subscriptions/$SubId" |
55 | | -} else { |
56 | | - $Scope = "/subscriptions/$SubId/resourceGroups/$RgName" |
57 | | -} |
| 62 | +```powershell |
| 63 | +# Target Paid, both Linux and Windows, but only for resources with missing LicenseType or LicenseOnly (do not target PAYG) |
| 64 | +.\scripts\deployment.ps1 -ManagementGroupId "<management-group-id>" -TargetLicenseType "Paid" -LicenseTypesToOverwrite @("Unspecified","LicenseOnly") |
58 | 65 |
|
59 | | -az account set --subscription $SubId |
| 66 | +# Target PAYG, but only where current LicenseType is Paid (do not target missing or LicenseOnly) |
| 67 | +.\scripts\deployment.ps1 -ManagementGroupId "<management-group-id>" -ExtensionType "Linux" -TargetLicenseType "PAYG" -LicenseTypesToOverwrite @("Paid") |
60 | 68 |
|
61 | | -az policy assignment create ` |
62 | | - --name "LicenseType-SQLArc-Assign" ` |
63 | | - --policy "LicenseType-SQLArc" ` |
64 | | - --scope "$Scope" ` |
65 | | - --params "{ \"effect\": { \"value\": \"DeployIfNotExists\" }, \"requiredLicenseType\": { \"value\": \"$RequiredLicenseType\" } }" ` |
66 | | - --mi-system-assigned ` |
67 | | - --role "Contributor" ` |
68 | | - --identity-scope "$Scope" ` |
69 | | - --location "$Location" ` |
70 | | - --only-show-errors | Out-Null |
| 69 | +# Overwrite all known existing LicenseType values (Paid, PAYG, LicenseOnly), but not missing |
| 70 | +.\scripts\deployment.ps1 -ManagementGroupId "<management-group-id>" -ExtensionType "Linux" -TargetLicenseType "Paid" -LicenseTypesToOverwrite @("Paid","PAYG","LicenseOnly") |
71 | 71 | ``` |
72 | 72 |
|
73 | | -## Create remediation task |
74 | | - |
75 | | -Use the following command to create a remediation task |
76 | | - |
77 | | -```PowerShell |
78 | | -
|
79 | | -$RemediationName = "Remediate-LicenseType-SQLArc" |
80 | | -$PolicyAssignmentName = "LicenseType-SQLArc-Assign" |
81 | | -$SubId = "<your-subscription-id>" |
82 | | -$RgName = "<your-resource-group>" |
83 | | -
|
84 | | -az account set --subscription $SubId |
85 | | -
|
86 | | -if ([string]::IsNullOrWhiteSpace($RgName)) { |
87 | | - az policy remediation create ` |
88 | | - --name $RemediationName ` |
89 | | - --policy-assignment $PolicyAssignmentName ` |
90 | | - --resource-discovery-mode ReEvaluateCompliance ` |
91 | | - --only-show-errors | Out-Null |
92 | | -} else { |
93 | | - az policy remediation create ` |
94 | | - --name $RemediationName ` |
95 | | - --policy-assignment $PolicyAssignmentName ` |
96 | | - --resource-group "$RgName" ` |
97 | | - --resource-discovery-mode ReEvaluateCompliance ` |
98 | | - --only-show-errors | Out-Null |
99 | | -} |
100 | | -``` |
| 73 | +Note: `scripts/deployment.ps1` automatically grants required roles to the policy assignment managed identity at assignment scope, preventing common `PolicyAuthorizationFailed` errors during DeployIfNotExists deployments. |
| 74 | + |
| 75 | +## Start Remediation |
101 | 76 |
|
102 | | -## Remove remediation task |
103 | | - |
104 | | -```PowerShell |
105 | | -
|
106 | | -$RemediationName = "Remediate-LicenseType-SQLArc" |
107 | | -$RgName = "<your-resource-group>" |
108 | | -$SubId = "<your-subscription-id>" |
109 | | -
|
110 | | -if ([string]::IsNullOrWhiteSpace($RgName)) { |
111 | | - az policy remediation cancel ` |
112 | | - --name $RemediationName ` |
113 | | - --subscription $SubId ` |
114 | | - --only-show-errors | Out-Null |
115 | | - az policy remediation delete ` |
116 | | - --name $RemediationName ` |
117 | | - --subscription $SubId ` |
118 | | - --only-show-errors | Out-Null |
119 | | -} else { |
120 | | - az policy remediation cancel ` |
121 | | - --name $RemediationName ` |
122 | | - --resource-group $RgName ` |
123 | | - --subscription $SubId ` |
124 | | - --only-show-errors | Out-Null |
125 | | - az policy remediation delete ` |
126 | | - --name $RemediationName ` |
127 | | - --resource-group $RgName ` |
128 | | - --subscription $SubId ` |
129 | | - --only-show-errors | Out-Null |
130 | | -} |
| 77 | +Parameter reference: |
| 78 | + |
| 79 | +| Parameter | Required | Default | Allowed values | Description | |
| 80 | +|---|---|---|---|---| |
| 81 | +| `ManagementGroupId` | Yes | N/A | Any valid management group ID | Used to resolve the policy definition/assignment naming context. | |
| 82 | +| `ExtensionType` | No | `Both` | `Windows`, `Linux`, `Both` | Must match the platform used for the assignment. When `Both` (default), remediates the combined assignment. | |
| 83 | +| `SubscriptionId` | No | Not set | Any valid subscription ID | If provided, remediation runs at subscription scope. | |
| 84 | +| `TargetLicenseType` | Yes | N/A | `Paid`, `PAYG` | Must match the assignment target license type. | |
| 85 | +| `GrantMissingPermissions` | No | `false` | Switch (`present`/`not present`) | If set, checks and assigns missing required roles before remediation. | |
| 86 | + |
| 87 | +```powershell |
| 88 | +# Example: remediate both platforms (default) |
| 89 | +.\scripts\start-remediation.ps1 -ManagementGroupId "<management-group-id>" -SubscriptionId "<subscription-id>" -TargetLicenseType "PAYG" -GrantMissingPermissions |
| 90 | +
|
| 91 | +# Example: remediate only Linux |
| 92 | +.\scripts\start-remediation.ps1 -ManagementGroupId "<management-group-id>" -ExtensionType "Linux" -SubscriptionId "<subscription-id>" -TargetLicenseType "PAYG" -GrantMissingPermissions |
131 | 93 | ``` |
| 94 | + |
| 95 | +## Managed Identity And Roles |
| 96 | + |
| 97 | +The policy assignment is created with `-IdentityType SystemAssigned`. Azure creates a managed identity on the assignment and uses it to apply DeployIfNotExists changes during enforcement and remediation. |
| 98 | + |
| 99 | +Required roles: |
| 100 | + |
| 101 | +- `Azure Extension for SQL Server Deployment` (`7392c568-9289-4bde-aaaa-b7131215889d`) |
| 102 | +- `Reader` (`acdd72a7-3385-48ef-bd42-f606fba81ae7`) |
| 103 | +- `Resource Policy Contributor` (required so DeployIfNotExists can create template deployments) |
| 104 | + |
| 105 | +## Troubleshooting |
| 106 | + |
| 107 | +If you see `PolicyAuthorizationFailed`, the policy assignment identity is missing one or more required roles at assignment scope (or inherited scope), often causing missing `Microsoft.HybridCompute/machines/extensions/write` permission. |
| 108 | + |
| 109 | +Use one of these options: |
| 110 | + |
| 111 | +- Re-run `scripts/deployment.ps1` (default behavior assigns `Resource Policy Contributor` automatically). |
| 112 | +- Re-run `scripts/deployment.ps1` (default behavior assigns required roles automatically). |
| 113 | +- Run `scripts/start-remediation.ps1 -GrantMissingPermissions` (checks and assigns missing required roles before remediation). |
| 114 | + |
| 115 | +## Screenshots |
| 116 | + |
| 117 | + |
| 118 | + |
| 119 | + |
| 120 | + |
0 commit comments