#### Prerequisites

You need the Azure CLI 2.0 tools to create a service principal for access to your DNS Zone.

Either install Azure CLI 2.0 locally or use the Azure Cloud Shell in Bash mode.

(See the [
Azure Command-Line Interface (CLI) documentation](https://learn.microsoft.com/en-us/cli/azure/?view=azure-cli-latest) for more details)

#### Log-in to Azure

(Not required when using the Azure Cloud Shell)

```
az login 
```

```json
[
  {
    "cloudName": "AzureCloud",
    "id": "12345678-9abc-def0-1234-567890abcdef",
    "isDefault": true,
    "name": "myAzureSubscription",
    "state": "Enabled",
    "tenantId": "11111111-2222-3333-4444-555555555555",
    "user": {
      "name": "someone@example.com",
      "type": "user"
    }
  }
]
```

#### Set your Azure subscription if you have more than one

```
az account list
```

```json
[
  {
    "cloudName": "AzureCloud",
    "homeTenantId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "id": "baaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "isDefault": true,
    "managedByTenants": [],
    "name": "Subscription A",
    "state": "Enabled",
    "tenantId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "user": {
      "cloudShellID": true,
      "name": "email@example.com",
      "type": "user"
    }
  },
  {
    "cloudName": "AzureCloud",
    "homeTenantId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "id": "caaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "isDefault": false,
    "managedByTenants": [],
    "name": "Subscription B",
    "state": "Enabled",
    "tenantId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "user": {
      "cloudShellID": true,
      "name": "email@example.com",
      "type": "user"
    }
  }
]
```

```
az account set --subscription "Subscription B"
```

#### List your DNS Zones

```
az network dns zone list
```

```json
[
  {
    "etag": "00000002-0000-0000-f641-73c64955d301",
    "id": "/subscriptions/12345678-9abc-def0-1234-567890abcdef/resourceGroups/exampledns_rg/providers/Microsoft.Network/dnszones/example.com",
    "location": "global",
    "maxNumberOfRecordSets": 5000,
    "name": "example.com",
    "nameServers": [
      "ns1-02.azure-dns.com.",
      "ns2-02.azure-dns.net.",
      "ns3-02.azure-dns.org.",
      "ns4-02.azure-dns.info."
    ],
    "numberOfRecordSets": 11,
    "resourceGroup": "exampledns_rg",
    "tags": {},
    "type": "Microsoft.Network/dnszones"
  }
]
```

#### Create a service principal

The service principal is used to grant acme.sh access to the DNS Zone using the id value from the previous commands output

(See the [az ad sp create-for-rbac](https://learn.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az-ad-sp-create-for-rbac) documentation for more details)  

```
az ad sp create-for-rbac --name  "AcmeDnsValidator" --role "DNS Zone Contributor" --scopes \
    /subscriptions/12345678-9abc-def0-1234-567890abcdef/resourceGroups/exampledns_rg/providers/Microsoft.Network/dnszones/example.com
```

```json
{
  "appId": "3b5033b5-7a66-43a5-b3b9-a36b9e7c25ed",
  "displayName": "AcmeDnsValidator",
  "name": "http://AcmeDnsValidator",
  "password": "e.L8Q~4jGhWHheCKjdRzw3gyBBwOmrTyYF9NYbxs",
  "tenant": "11111111-2222-3333-4444-555555555555"
}
```

##### Note: Dealing with multiple DNS Zones

If you are managing certificates for multiple DNS Zones, you can create the service principal with multiple scopes.

For example, if you are managing certificates for both `example.com` and `example.edu`, you can create the service principal with both scopes:

```
az ad sp create-for-rbac --name  "AcmeDnsValidator" --role "DNS Zone Contributor" --scopes \
    /subscriptions/12345678-9abc-def0-1234-567890abcdef/resourceGroups/exampledns_rg/providers/Microsoft.Network/dnszones/example.com \
    /subscriptions/12345678-9abc-def0-1234-567890abcdef/resourceGroups/exampledns2_rg/providers/Microsoft.Network/dnszones/example.edu
```

Or if the service principal has already been created, you can grant it access to the additional scope:

```
az ad sp list --filter "displayname eq 'AcmeDnsValidator'" | grep '^    \"id\":'
```

(The `grep` above is assuming a json array of nested lists is returned with a tab size of two spaces and is finding the top-level `id`)

```json
    "id": "daaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
```

```
az role assignment create --assignee daaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa --role "DNS Zone Contributor" --scope \
    /subscriptions/12345678-9abc-def0-1234-567890abcdef/resourceGroups/deleteme_rg/providers/Microsoft.Network/dnszones/example.edu
```

##### Note: Dealing with multiple credentials

By default acme.sh saves credentials in `~/.acme.sh/account.conf` and these credentials are used for all DNS zones.

If you want to use different credentials, use the `--accountconf` switch to specify a configuration file.

#### Limit access permissions to TXT records 

In Azure DNS you can limit the permissions for the service principal further and only grant permissions to modifiy TXT records for a given DNS Zone.

(See [How to protect DNS zones and records](https://learn.microsoft.com/en-us/azure/dns/dns-protect-zones-recordsets) for more details)

Example:

* Azure Subscription is `12345678-9abc-def0-1234-567890abcdef`
* The resource group of your DNS Zone is `exampledns_rg`
* The DNS Zone is `example.com`

```sh
#!/usr/bin/env sh
# Create a custom RBAC role that grants permissions to modify only TXT records
dnscustomrole='{ 
    "Name": "DNS TXT Contributor", 
    "Id": "",
     "IsCustom": true, 
    "Description": "Can manage DNS TXT records only.", 
    "Actions": [ 
        "Microsoft.Network/dnsZones/TXT/*", 
        "Microsoft.Network/dnsZones/read", 
        "Microsoft.Authorization/*/read", 
        "Microsoft.Insights/alertRules/*", 
        "Microsoft.ResourceHealth/availabilityStatuses/read", 
        "Microsoft.Resources/deployments/read", 
        "Microsoft.Resources/subscriptions/resourceGroups/read" 
    ],
    "NotActions": [ 
    ],
    "AssignableScopes": [ 
        "/subscriptions/12345678-9abc-def0-1234-567890abcdef" 
    ] 
}'
az role definition create --role-definition "$dnscustomrole"
# Create a new service principal and grant permissions to modify TXT recornds in the give DNS Zone
az ad sp create-for-rbac --name  "AcmeDnsValidator" --role "DNS TXT Contributor" --scopes "/subscriptions/12345678-9abc-def0-1234-567890abcdef/resourceGroups/exampledns_rg/providers/Microsoft.Network/dnszones/example.com" 

# or  grant an existing service principal permissions to modify TXT recornds in the give DNS Zone
#az role assignment create  --assignee 3b5033b5-7a66-43a5-b3b9-a36b9e7c25ed --scope "/subscriptions/12345678-9abc-def0-1234-567890abcdef/resourceGroups/exampledns_rg/providers/Microsoft.Network/dnszones/example.com" --role "DNS TXT Contributor"
```

#### You can now use acme.sh 

```
export AZUREDNS_SUBSCRIPTIONID="12345678-9abc-def0-1234-567890abcdef"
export AZUREDNS_TENANTID="11111111-2222-3333-4444-555555555555"
export AZUREDNS_APPID="3b5033b5-7a66-43a5-b3b9-a36b9e7c25ed"          # appid of the service principal
export AZUREDNS_CLIENTSECRET="e.L8Q~4jGhWHheCKjdRzw3gyBBwOmrTyYF9NYbxs"   # password from creating the service principal

acme.sh --issue --dns dns_azure -d example.com -d www.example.com
```

#### Update service principal password

The service principal credentials may eventually expire.

Some acme.sh renewal errors that are signs of the credentials expiring:

- `no acccess token received. Check your Azure settings`
- `access denied make sure your Azure settings are correct`

```
az ad sp list --filter "displayname eq 'AcmeDnsValidator'" | grep '^    \"id\":'
```

(The `grep` above is assuming a json array of nested lists is returned with a tab size of two spaces and is finding the top-level `id`)

```json
    "id": "daaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
```

```
az ad sp credential reset --id daaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
```

Update `~/.acme.sh/account.conf` with the new credentials.

(See [az ad sp credential](https://learn.microsoft.com/en-us/cli/azure/ad/sp/credential?view=azure-cli-latest#az-ad-sp-credential-reset) for details)