Home Resolving Conflicts for Private Endpoint DNS
Post
Cancel

Resolving Conflicts for Private Endpoint DNS

Automating the process of creating Private Endpoint DNS configurations in their respective zones is key for a successful private networking implementation in Azure.

Record creation can be done using a set of Azure policies. However, not all of the built-in policies are able to deal with subresources of the same name being targeted to different zones. This may lead to records being created in the wrong zones.

This post will look at how we can solve this by adding another filter to the policies, and how we can manage subresources that require DNS records in multiple zones.

Check out my PrivateEndpoint repo for a good start!

Subresources

All the subresources can be found here.

Looking through the list, it will become obvious that some subresources are mentioned multiple times, and need to be targeted to different privatelink zones.

For example, Sql can be found for Synapse Analytics and Cosmos DB.

subresources_sql

Account for Cognitive Services and Purview.

subresources_account

Namespace for Event Hubs, Service Bus, and Relay. In this case it doesn’t matter though, as all are targeting the same privatelink zone.

subresources_namespace

The Private Endpoint DNS policy

Looking at the typical policy that creates the DNS records, we can see that at row 22 the policy is only checking that:

Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*].groupIds is equal to Sql.

This could mess with the creation of records, as depending on which policy hits the record will be created in that zone. The DNS record for your Synapse Analytics could be created in the Cosmos DB privatelink zone, and vice versa.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
{
  "parameters": {
    "privateDnsZoneId": {
      "type": "String",
      "metadata": {
        "displayName": "privateDnsZoneId",
        "strongType": "Microsoft.Network/privateDnsZones"
      }
    }
  },
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "type",
          "equals": "Microsoft.Network/privateEndpoints"
        },
        {
          "count": {
            "field": "Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*].groupIds[*]",
            "where": {
              "field": "Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*].groupIds[*]",
              "equals": "Sql"
            }
          },
          "greaterOrEquals": 1
        }
      ]
    },
    "then": {
      "effect": "deployIfNotExists",
      "details": {
        "roleDefinitionIds": [
          "/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7"
        ],
        "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
        "deployment": {
          "properties": {
            "mode": "Incremental",
            "template": {
              "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
              "contentVersion": "1.0.0.0",
              "parameters": {
                "privateDnsZoneId": {
                  "type": "String"
                },
                "privateEndpointName": {
                  "type": "String"
                },
                "location": {
                  "type": "String"
                }
              },
              "resources": [
                {
                  "name": "[concat(parameters('privateEndpointName'), '/deployedByPolicy')]",
                  "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
                  "apiVersion": "2020-03-01",
                  "location": "westeurope",
                  "properties": {
                    "privateDnsZoneConfigs": [
                      {
                        "name": "CosmosSql-privateDnsZone",
                        "properties": {
                          "privateDnsZoneId": "[parameters('privateDnsZoneId')]"
                        }
                      }
                    ]
                  }
                }
              ]
            },
            "parameters": {
              "privateDnsZoneId": {
                "value": "[parameters('privateDnsZoneId')]"
              },
              "privateEndpointName": {
                "value": "[field('name')]"
              },
              "location": {
                "value": "[field('location')]"
              }
            }
          }
        }
      }
    }
  }
}

The better Private Endpoint DNS policy

The solution is rather simple. In the policy, we need to specify the resource type when filtering the privateLinkServiceConnections property. Now this policy will only target Synapse Analytics Private Endpoints, and not be able to create records for types related to Cosmos DB.

This can be seen at rows 19-20 where the type has been added.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
{
  "parameters": {
    "privateDnsZoneId": {
      "type": "String",
      "metadata": {
        "displayName": "privateDnsZoneId",
        "strongType": "Microsoft.Network/privateDnsZones"
      }
    }
  },
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "type",
          "equals": "Microsoft.Network/privateEndpoints"
        },
        {
          "field": "Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*].privateLinkServiceId",
          "contains": "Microsoft.Synapse/workspaces"
        },
        {
          "count": {
            "field": "Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*].groupIds[*]",
            "where": {
              "field": "Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*].groupIds[*]",
              "equals": "Sql"
            }
          },
          "greaterOrEquals": 1
        }
      ]
    },
    "then": {
      "effect": "deployIfNotExists",
      "details": {
        "roleDefinitionIds": [
          "/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7"
        ],
        "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
        "deployment": {
          "properties": {
            "mode": "Incremental",
            "template": {
              "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
              "contentVersion": "1.0.0.0",
              "parameters": {
                "privateDnsZoneId": {
                  "type": "String"
                },
                "privateEndpointName": {
                  "type": "String"
                },
                "location": {
                  "type": "String"
                }
              },
              "resources": [
                {
                  "name": "[concat(parameters('privateEndpointName'), '/deployedByPolicy')]",
                  "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
                  "apiVersion": "2020-03-01",
                  "location": "westeurope",
                  "properties": {
                    "privateDnsZoneConfigs": [
                      {
                        "name": "CosmosSql-privateDnsZone",
                        "properties": {
                          "privateDnsZoneId": "[parameters('privateDnsZoneId')]"
                        }
                      }
                    ]
                  }
                }
              ]
            },
            "parameters": {
              "privateDnsZoneId": {
                "value": "[parameters('privateDnsZoneId')]"
              },
              "privateEndpointName": {
                "value": "[field('name')]"
              },
              "location": {
                "value": "[field('location')]"
              }
            }
          }
        }
      }
    }
  }
}

Policy package repository

I have created a Github repo with some Bicep templates that will create all the policy definitions, and include them in a policy initiative. All definitions are created with the resource type filter.

The repo is updated when new services receive support for Private Endpoints, and everyone is free to contribute.

Private Endpoint repo

Subresources with multiple zones

There’s currently no support in the repo for services that require the same subresource to be registered to multiple privatelink zones.

For this, we need to create all the privateDnsZoneConfigs in the privateDnsZoneGroups resource in the same policy. All the configs must be added using one array, as Private Endpoints don’t support multiple privateDnsZoneGroups.

Looking at the subresource list again, an example for this is amlworkspace that requires DNS records in two zones.

azureml-zones

The below policy showcases how this can be done. Notice we now have 2 parameters and 2 privateDnsZoneConfigs as we have 2 zones. Other subresources may require more.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
{
  "parameters": {
    "privateDnsZoneIdApi": {
      "type": "String",
      "metadata": {
        "displayName": "privateDnsZoneIdApi",
        "strongType": "Microsoft.Network/privateDnsZones"
      }
    },
    "privateDnsZoneIdNotebook": {
      "type": "String",
      "metadata": {
        "displayName": "privateDnsZoneIdNotebook",
        "strongType": "Microsoft.Network/privateDnsZones"
      }
    }
  },
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "type",
          "equals": "Microsoft.Network/privateEndpoints"
        },
        {
          "field": "Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*].privateLinkServiceId",
          "contains": "Microsoft.MachineLearningServices/workspaces"
        },
        {
          "count": {
            "field": "Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*].groupIds[*]",
            "where": {
              "field": "Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*].groupIds[*]",
              "equals": "amlworkspace"
            }
          },
          "greaterOrEquals": 1
        }
      ]
    },
    "then": {
      "effect": "deployIfNotExists",
      "details": {
        "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
        "roleDefinitionIds": [
          "/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7"
        ],
        "existenceCondition": {
          "allOf": [
            {
              "field": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups/privateDnsZoneConfigs[*].privateDnsZoneId",
              "equals": "[parameters('privateDnsZoneIdApi')]"
            },
            {
              "field": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups/privateDnsZoneConfigs[*].privateDnsZoneId",
              "equals": "[parameters('privateDnsZoneIdNotebook')]"
            }
          ]
        },
        "deployment": {
          "properties": {
            "mode": "incremental",
            "template": {
              "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
              "contentVersion": "1.0.0.0",
              "parameters": {
                "privateDnsZoneIdApi": {
                  "type": "String"
                },
                "privateDnsZoneIdNotebook": {
                  "type": "String"
                },
                "privateEndpointName": {
                  "type": "String"
                },
                "location": {
                  "type": "String"
                }
              },
              "resources": [
                {
                  "name": "[concat(parameters('privateEndpointName'), '/deployedByPolicy')]",
                  "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
                  "apiVersion": "2020-03-01",
                  "location": "[parameters('location')]",
                  "properties": {
                    "privateDnsZoneConfigs": [
                      {
                        "name": "amlworkspaceApi-privateDnsZone",
                        "properties": {
                          "privateDnsZoneId": "[parameters('privateDnsZoneIdApi')]"
                        }
                      },
                      {
                        "name": "amlworkspaceNotebook-privateDnsZone",
                        "properties": {
                          "privateDnsZoneId": "[parameters('privateDnsZoneIdNotebook')]"
                        }
                      }
                    ]
                  }
                }
              ]
            },
            "parameters": {
              "privateDnsZoneIdApi": {
                "value": "[parameters('privateDnsZoneIdApi')]"
              },
              "privateDnsZoneIdNotebook": {
                "value": "[parameters('privateDnsZoneIdNotebook')]"
              },
              "privateEndpointName": {
                "value": "[field('name')]"
              },
              "location": {
                "value": "[field('location')]"
              }
            }
          }
        }
      }
    }
  }
}

And the resulting configuration on the Private Endpoint from the policy.

amlworkspace-records

This post is licensed under CC BY 4.0 by the author.