Je me suis donc décidé à vous écrire plusieurs articles pour vous aider à utiliser les private endpoint sur Azure de la manière la plus sereine possible. On va donc voir dans ces articles les sujets suivants :
Et si bien entendu vous pensez qu’un autre sujet mérite d’être creusé il y a les commentaires pour cela.
Avant de commencer, j’ai partagé tous les scripts que j’utilise dans ce repository Github, n’hésitez pas à contribuer, et n’oublier pas de bien supprimer vos ressources dès que vous avez fini vos tests.
On va commencer par les basiques à savoir la résolution DNS de votre Private Endpoint depuis votre compute lorsque est dans une architecture très basique. Il nous faut donc :
Cela nous donne donc le schéma suivant :
Ici en terme de configuration, on va garder les éléments le plus standard possible, à savoir la configuration DNS de notre Virtual Network à Default (Azure Provided), comme ci-dessous :
La mise en place de cette configuration indique qu’on va utiliser le DNS Azure pour résoudre toutes nos routes, y compris celle pour résoudre l’adresse de mon blog https://woivre.fr ou celle pour résoudre à votre private endpoint.
Donc en terme de flux lorsqu’on veut résoudre notre private endpoint on fait donc les étapes suivantes :
Lorsqu’on fait notre nslookup, nous avons donc le résultat suivant :
[Run cmd]
nslookup labprivatelinkgi7jjcx.blob.core.windows.net
Enable succeeded:
[stdout]
Server: 127.0.0.53
Address: 127.0.0.53#53
Non-authoritative answer:
labprivatelinkgi7jjcx.blob.core.windows.net canonical name = labprivatelinkgi7jjcx.privatelink.blob.core.windows.net.
Name: labprivatelinkgi7jjcx.privatelink.blob.core.windows.net
Address: 10.0.0.4
Cependant tout est une question de DNS ici, si l’on passe par un DNS autre, on obtient un tout autre résultat
[Run cmd]
nslookup labprivatelinkgi7jjcx.blob.core.windows.net 8.8.8.8
Enable succeeded:
[stdout]
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
labprivatelinkgi7jjcx.blob.core.windows.net canonical name = labprivatelinkgi7jjcx.privatelink.blob.core.windows.net.
labprivatelinkgi7jjcx.privatelink.blob.core.windows.net canonical name = blob.ams09prdstr13a.store.core.windows.net.
Name: blob.ams09prdstr13a.store.core.windows.net
Address: 20.60.222.129
D’un point de vue de la résolution DNS, notre Storage est bien toujours présent lorsqu’on fait des DNS lookup, cela n’influe en rien si notre storage est accessible depuis l’extérieur ou non. Pour bloquer l’accès depuis l’extérieur, il faudra bien penser à choisir la bonne option dans la partie networking de votre Storage.
Maintenant que la partie simple est abordée, regardons comment exposer notre storage account à un client extérieur, qui peut être sur Azure ou sur toute autre infrastructure. On aura donc le schéma suivant :
En terme de résolution DNS on aura donc quelque chose de très simple depuis notre client B qui est la même que lorsque nous avons tenté de résoudre notre domaine depuis le DNS de Google. Je ne vais pas donc détailler plus ce point là.
Notre dernier use case que nous allons aborder est le cas où vos deux clients ont un setup de private dns zone qui lui est propre, mais que le customer B a besoin d’appeler le customer A via son adresse publique comme on peut le voir sur le schéma ci-dessous.
Si on retente les mêmes commandes de nslookup, nous allons voir qu’il y a un soucis lorsqu’on utilise le DNS fourni par Microsoft:
[VM]
vm
[Run cmd]
nslookup labprivatelink7ev3eoy.blob.core.windows.net
Enable succeeded:
[stdout]
Server: 127.0.0.53
Address: 127.0.0.53#53
** server can't find labprivatelink7ev3eoy.blob.core.windows.net: NXDOMAIN
[stderr]
------------------------------------------
[VM]
vm
[Run cmd]
nslookup labprivatelink7ev3eoy.blob.core.windows.net 8.8.8.8
Enable succeeded:
[stdout]
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
labprivatelink7ev3eoy.blob.core.windows.net canonical name = labprivatelink7ev3eoy.privatelink.blob.core.windows.net.
labprivatelink7ev3eoy.privatelink.blob.core.windows.net canonical name = blob.ams09prdstr07a.store.core.windows.net.
Name: blob.ams09prdstr07a.store.core.windows.net
Address: 20.60.223.100
[stderr]
------------------------------------------
Depuis les DNS de Google, nous n’avons pas de soucis, mais depuis le Azure Recursive Resolver cela ne fonctionne pas, car si l’on reprend le schéma suivant:
Lors de l’étape 2, Azure nous renvoie un CNAME vers le private link que mon client B n’a pas. Ce scénario est valable que vous soyez dans la même souscription, le même tenant, ou le même environnement Azure, donc dans 99% des cas. La seule exception est si un de vos deux clients utilisent Azure China par exemple.
Pour résoudre cela, il existe plusieurs moyens:
Si on choisit l’option Azure DNS resolver, notre client B doit mettre en place un ruleset pour accéder au storage de notre client A. Ce ruleset aura la configuration suivante
resource rule 'Microsoft.Network/dnsForwardingRulesets/forwardingRules@2022-07-01' = {
parent: ruleset
name: '${deployment().name}-google-rule'
properties: {
domainName: '${storageDomainName}.'
targetDnsServers: [
{
ipAddress: '8.8.8.8'
}
]
}
}
Ce qui nous donnera au final le template ARM suivant
{
"type": "Microsoft.Network/dnsForwardingRulesets/forwardingRules",
"apiVersion": "2022-07-01",
"name": "dnsresolver-04-dnsresolver-customerB-ruleset/dnsresolver-04-dnsresolver-customerB-google-rule",
"dependsOn": [
"[resourceId('Microsoft.Network/dnsForwardingRulesets', 'dnsresolver-04-dnsresolver-customerB-ruleset')]"
],
"properties": {
"domainName": "labprivatelinkawdaptz.blob.core.windows.net.",
"targetDnsServers": [
{
"ipAddress": "8.8.8.8",
"port": 53
}
],
"forwardingRuleState": "Enabled"
}
}
Et nous voyons dans notre test que la magie opère, et que nous pouvons bien résoudre l’ip de notre blob storage via Google. Mais pas celui du deuxième storage créé
[VM]
vm
[Run cmd]
nslookup labprivatelinkawdaptz.blob.core.windows.net
Enable succeeded:
[stdout]
Server: 127.0.0.53
Address: 127.0.0.53#53
Non-authoritative answer:
labprivatelinkawdaptz.blob.core.windows.net canonical name = labprivatelinkawdaptz.privatelink.blob.core.windows.net.
labprivatelinkawdaptz.privatelink.blob.core.windows.net canonical name = blob.ams09prdstr13c.store.core.windows.net.
Name: blob.ams09prdstr13c.store.core.windows.net
Address: 20.209.10.97
[stderr]
------------------------------------------
[VM]
vm
[Run cmd]
nslookup labprivatelinkawdaptz.blob.core.windows.net 8.8.8.8
Enable succeeded:
[stdout]
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
labprivatelinkawdaptz.blob.core.windows.net canonical name = labprivatelinkawdaptz.privatelink.blob.core.windows.net.
labprivatelinkawdaptz.privatelink.blob.core.windows.net canonical name = blob.ams09prdstr13c.store.core.windows.net.
Name: blob.ams09prdstr13c.store.core.windows.net
Address: 20.209.10.97
[stderr]
------------------------------------------
[VM]
vm
[Run cmd]
nslookup labprivatelinkr6lcrfw.blob.core.windows.net
Enable succeeded:
[stdout]
Server: 127.0.0.53
Address: 127.0.0.53#53
** server can't find labprivatelinkr6lcrfw.blob.core.windows.net: NXDOMAIN
[stderr]
------------------------------------------
[VM]
vm
[Run cmd]
nslookup labprivatelinkr6lcrfw.blob.core.windows.net 8.8.8.8
Enable succeeded:
[stdout]
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
labprivatelinkr6lcrfw.blob.core.windows.net canonical name = labprivatelinkr6lcrfw.privatelink.blob.core.windows.net.
labprivatelinkr6lcrfw.privatelink.blob.core.windows.net canonical name = blob.ams20prdstr15a.store.core.windows.net.
Name: blob.ams20prdstr15a.store.core.windows.net
Address: 20.209.108.75
[stderr]
------------------------------------------
A savoir que ce genre de problème ne se pose pas qu’avec différents clients. Au sein d’une même souscription, si vous avez un storage publique et que vous le connectez via un Managed Private Endpoint à une instance de Synapse vous aurez le même comportement, et il faudra jouer sur les DNS ou passer sur une solution de private endpoints.
]]>Même si aujourd’hui, vous ne pouvez plus en créer, Microsoft s’est appuyé dessus pour un bon nombre de services PAAS, et il l’heure de switcher sur des nouvelles versions. Et pour la plupart de ces composants, cela nécessite une opération de votre part. Où alors vous pouvez attendre que Microsoft vous force la migration, mais vous n’aurez pas la main en cas d’échec.
Parmi les services que j’ai en tête, nous avons:
Tous ces produits ont des chemins de migration que vous pouvez suivre. Mais bien entendu, vous ne pouvez passer votre temps sur les mises à jour d’Azure pour construire votre roadmap applicative.
Pour vous aider, Microsoft fourni un workbook Service Retirements dans la partie Advisor, dans celui ci il vous liste tous les services qui seront supprimés à l’avenir, et il peut même vous donner les instances que vous utilisez, il ne s’agit donc pas d’une liste à la Prévert sur laquelle vous devez trier.
Et si vous êtes allergique à l’UI, il est toujours possible de faire cela via une requête Ressoure Graph
advisorresources
| project id, properties.impact, properties.shortDescription.problem
]]>La gestion des DNS d’un storage n’est pas la même en fonction des SKUs, ce qui fait que si vous utilisez des proxy pour accéder à des storages, vous pouvez avoir des mauvaises surprises.
Pour vous montrer cela, on va commencer par créer un storage par type de SKU avec ce bicep:
var skus = [
'Standard_LRS'
'Standard_ZRS'
'Standard_GRS'
'Standard_GZRS'
'Standard_RAGRS'
'Standard_RAGZRS'
'Premium_LRS'
'Premium_ZRS'
]
resource storage 'Microsoft.Storage/storageAccounts@2023-01-01' = [for (item, index) in skus: {
name: 'stodns${uniqueString(deployment().name)}${index}'
location: resourceGroup().location
kind: 'StorageV2'
sku: {
name: item
}
properties: {
accessTier: 'Hot'
minimumTlsVersion: 'TLS1_2'
}
}]
On va maintenant faire un simple nslookup
$storages = Get-AzStorageAccount -ResourceGroupName "dns-storage-rg"
foreach ($storage in $storages) {
Write-Output "------------------------------"
Write-Output "Storage Account $($storage.StorageAccountName) with the SKU $($storage.Sku.Name)"
nslookup "$($storage.StorageAccountName).blob.core.windows.net"
}
Et on voit dans les résultats des choses intéressantes:
------------------------------
Storage Account stodnsrbgf3xv4ufgzg0 with the SKU Standard_LRS
Server: UnKnown
Address: 10.104.244.68
Name: blob.amz06prdstr04c.store.core.windows.net
Address: 20.38.109.228
Aliases: stodnsrbgf3xv4ufgzg0.blob.core.windows.net
------------------------------
Storage Account stodnsrbgf3xv4ufgzg1 with the SKU Standard_ZRS
Server: UnKnown
Address: 10.104.244.68
Name: blob.AMS07PrdStrz04A.trafficmanager.net
Addresses: 20.150.9.196
20.150.76.4
20.150.9.228
Aliases: stodnsrbgf3xv4ufgzg1.blob.core.windows.net
blob.ams07prdstrz04a.store.core.windows.net
------------------------------
Storage Account stodnsrbgf3xv4ufgzg2 with the SKU Standard_GRS
Server: UnKnown
Address: 10.104.244.68
Name: blob.ams20prdstr15a.store.core.windows.net
Address: 20.209.108.75
Aliases: stodnsrbgf3xv4ufgzg2.blob.core.windows.net
------------------------------
Storage Account stodnsrbgf3xv4ufgzg3 with the SKU Standard_GZRS
Server: UnKnown
Address: 10.104.244.68
Name: blob.AMS07PrdStrz10A.trafficmanager.net
Addresses: 20.209.193.33
20.209.231.33
20.209.193.65
Aliases: stodnsrbgf3xv4ufgzg3.blob.core.windows.net
blob.ams07prdstrz10a.store.core.windows.net
------------------------------
Storage Account stodnsrbgf3xv4ufgzg4 with the SKU Standard_RAGRS
Server: UnKnown
Address: 10.104.244.68
Name: blob.ams23prdstr18a.store.core.windows.net
Address: 20.60.27.132
Aliases: stodnsrbgf3xv4ufgzg4.blob.core.windows.net
------------------------------
Storage Account stodnsrbgf3xv4ufgzg5 with the SKU Standard_RAGZRS
Server: UnKnown
Address: 10.104.244.68
Name: blob.AMS07PrdStrz10A.trafficmanager.net
Addresses: 20.209.231.33
20.209.193.65
20.209.193.33
Aliases: stodnsrbgf3xv4ufgzg5.blob.core.windows.net
blob.ams07prdstrz10a.store.core.windows.net
------------------------------
Storage Account stodnsrbgf3xv4ufgzg6 with the SKU Premium_LRS
Server: UnKnown
Address: 10.104.244.68
Name: blob.ams06prdstp06a.store.core.windows.net
Address: 52.239.212.228
Aliases: stodnsrbgf3xv4ufgzg6.blob.core.windows.net
------------------------------
Storage Account stodnsrbgf3xv4ufgzg7 with the SKU Premium_ZRS
Server: UnKnown
Address: 10.104.244.68
Name: blob2.AMS08PrdStfz01A.trafficmanager.net
Addresses: 20.209.109.130
20.209.108.2
20.209.108.162
Aliases: stodnsrbgf3xv4ufgzg7.blob.core.windows.net
blob2.ams08prdstfz01a.store.core.windows.net
On voit donc ici que tous les storages avec un SKU ayant une résilience de zone passent par un Traffic Manager dans la résolution de leur nom de domaine. Il faudra donc bien penser à ouvrir la résolution vers le domaine *.trafficmanager.net dans vos proxys.
]]>Toutes ces identités ont été supprimés de votre entra id, qu’il s’agisse d’un user, d’un groupe ou d’un SPN. Cependant Azure ne fait pas la ménage pour vous, et c’est à vous de le faire. Mais bonne nouvelle cela ne compte pas dans les role assignments effectifs et donc dans les limites c’est juste esthétique dans le portail.
Donc voici un petit script pour faire le ménage:
[CmdletBinding()]
param (
[switch] $DryRun,
[PSDefaultValue(Help='Current subscription')]
[Parameter(Mandatory = $false, HelpMessage="Use a valid azure scope")]
[string] $scope = ""
)
Connect-MgGraph -Scopes "Directory.Read.All" -NoWelcome
[array]$assignments = @()
if ("" -eq $scope) {
$assignments = Get-AzRoleAssignment
} else {
$assignments = Get-AzRoleAssignment -Scope $scope
}
Write-Output "Found $($assignments.Count) assignments"
foreach ($assignment in $assignments) {
Write-Verbose "Processing $($assignment.RoleAssignmentId)"
if ($null -eq (Get-MgDirectoryObject -DirectoryObjectId $assignment.ObjectId -ErrorAction SilentlyContinue)) {
Write-Output "Removing $($assignment.RoleAssignmentId)"
if (-not $DryRun) {
$assignment | Remove-AzRoleAssignment
}
}
}
L’interaction avec Azure se fait toujours via des REST API qui sont bien visible si vous faîtes de l’ARM ou du bicep, et malheureusement très souvent ignoré si vous utilisez Azure uniquement via AzCli ou Azure Powershell.
Prenons l’exemple d’EventHub avec les versions d’api 2017-04-01 et 2024-01-01, on peut voir qu’un bon nombre de propriété se sont ajouter au fur et à mesure du temps.
2024-01-01 | Disponible en 2017-04-01 |
---|---|
id | oui |
identity.principalId | non |
identity.tenantId | non |
identity.type | non |
identity.userAssignedIdentities | non |
location | oui |
name | oui |
properties.alternateName | non |
properties.clusterArmId | non |
properties.createdAt | oui |
properties.disableLocalAuth | non |
properties.encryption.keySource | non |
properties.encryption.keyVaultProperties | non |
properties.encryption.requireInfrastructureEncryption | non |
properties.isAutoInflateEnabled | oui |
properties.kafkaEnabled | oui |
properties.maximumThroughputUnits | oui |
properties.metricId | oui |
properties.minimumTlsVersion | non |
properties.privateEndpointConnections | non |
properties.provisioningState | oui |
properties.publicNetworkAccess | non |
properties.serviceBusEndpoint | oui |
properties.status | non |
properties.updatedAt | oui |
properties.zoneRedundant | non |
sku | oui |
systemData | non |
tags | oui |
type | oui |
Je n’ai qu’une chose à dire
Si vous avez lu mon précédent article sur Azure Policy vous êtes en droit de vous demander comment cela marche-t-il avec la propriété minimumTLSVersion. Et si vous ne vous posez pas la question, on va quand même y répondre ici.
On va donc créer 2 resources groups le premier avec deux policy Deny et Append comme suit:
Deny
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.EventHub/namespaces"
},
{
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"exists": true
},
{
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"notEquals": "1.2"
}
]
},
"then": {
"effect": "deny"
}
}
Append:
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.EventHub/namespaces"
},
{
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"exists": false
}
]
},
"then": {
"effect": "append",
"details": [
{
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"value": "1.2"
}
]
}
}
On va ensuite déployer notre bicep suivant avec la plus vieille version de l’API
resource eventhub 'Microsoft.EventHub/namespaces@2017-04-01' = {
name: 'wwo${deployment().name}${uniqueString(resourceGroup().id)}'
#disable-next-line no-loc-expr-outside-params
location: resourceGroup().location
sku: {
name: 'Basic'
capacity: 1
}
}
Cela nous donne donc cela:
New-AzrRsourceGroupDeployment -name test -ResourceGroupName eventhub-denyappendpolicy-rg -TemplateFile .\main.bicep | Out-Null
(Get-AzEventHubNamespace -ResourceGroupName eventhub-denyappendpolicy-rg).minimumTLSVersion
1.0
On peut voir ici que malgré nos Azure Policy, notre Event Hub est toujours avec un TLS minimal en 1.0. Mais bon selon Azure tout s’est bien passé lors du déploiement
Maintenant on va tenter de faire la même chose avec la policy Modify suivante:
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.EventHub/namespaces"
}
]
},
"then": {
"effect": "modify",
"details": {
"operations": [
{
"operation": "addOrReplace",
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"value": "1.2"
}
],
"roleDefinitionIds": [
"/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"
]
}
}
}
On lance donc la même commande powershell
New-AzResourceGroupDeploymen -name test -ResourceGroupName eventhub-modifypolicy-rg -TemplateFile .\main.bicep | Out-Null (Get-AzEventHubNamespace -ResourceGroupName eventhub-modifypolicy-rg).minimumTLSVersion
New-AzResourceGroupDeployment: 3:26:51 PM - Error: Code=InvalidTemplateDeployment; Message=The template deployment failed because of policy violation. Please see details for more information.
New-AzResourceGroupDeployment: 3:26:51 PM - Error: Code=NonModifiablePolicyAlias; Message=The aliases: 'Microsoft.EventHub/namespaces/minimumTlsVersion' are not modifiable in requests using API version: '2017-04-01'. This can happen in requests using API versions for which the aliases do not support the 'modify' effect, or support the 'modify' effect with a different token type.
New-AzResourceGroupDeployment: 3:26:51 PM - Error: Code=PolicyViolation; Message=Unable to apply 'modify' operation using the alias: 'Microsoft.EventHub/namespaces/minimumTlsVersion'. This alias is not modifiable in requests using API versions: '2021-11-01,2021-06-01-preview,2021-01-01-preview,2018-01-01-preview,2017-04-01,2015-08-01,2014-09-01'. See https://aka.ms/policy-modify-conflicts for details. Policies: '{"policyAssignment":{"name":"eventhub-modify-tls","id":"/subscriptions/9d854bbf-c6b3-4b03-a3de-cc4dc16cad0f/resourceGroups/eventhub-modifypolicy-rg/providers/Microsoft.Authorization/policyAssignments/9a2a2c2a500740c69c10bb47"},"policyDefinition":{"name":"eventhub-modify-tls","id":"/subscriptions/9d854bbf-c6b3-4b03-a3de-cc4dc16cad0f/providers/Microsoft.Authorization/policyDefinitions/9ea2d44b-9311-4896-8c2d-dd0cd7907e8f"}}'
New-AzResourceGroupDeployment: The deployment validation failed
Alors certes le déploiement ne fonctionne pas, mais on nous dit clairement que l’alias n’est pas supporté par l’api version que nous utilisons. Il ne nous reste qu’à update notre API Version dans notre template bicep.
]]>Après un échange avec un de mes collègues sur la gestion des policies, je pense qu’il est nécessaire d’expliquer certaines choses sur les différents types d’action, et surtout sur leur ordre d’execution.
Prenons par exemple le cas suivant: En tant que responsable de la sécurité, je souhaite interdire l’usage de TLS autre que 1.2 sur mes EventHubs
La première approche est donc de prendre textuellement la demande et l’appliquer sur une Azure policy qui ressemblera à ça :
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.EventHub/namespaces"
},
{
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"exists": true
},
{
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"notEquals": "1.2"
}
]
},
"then": {
"effect": "deny"
}
}
Après déploiement, un premier test est fait depuis le portail Azure, et bonne nouvelle, la policy fonctionne bien. Maintenant il faut rappeler que toute la gestion avec les providers Azure n’est qu’API, qu’est ce qu’il se passe si on ne met pas de minimalTLSVersion dans notre payload, ou alors dans notre bicep
resource eventhub 'Microsoft.EventHub/namespaces@2023-01-01-preview' = {
name: 'wwo${deployment().name}${uniqueString(resourceGroup().id)}'
#disable-next-line no-loc-expr-outside-params
location: resourceGroup().location
sku: {
name: 'Basic'
capacity: 1
}
}
Et bien mauvaise nouvelle pour le coup, notre event hub est bien créé, et en terme de sécurité il n’est donc pas compliant.
Pour forcer cela, il y a 3 solutions basés sur les policies.
Notre première approche consiste à modifier la policy dans ce sens:
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.EventHub/namespaces"
},
{
"anyOf": [
{
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"exists": false
},
{
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"notEquals": "1.2"
}
]
}
]
},
"then": {
"effect": "deny"
}
}
De ce fait on force la présence du champ minimalTLSVersion même sur l’API, et de ce fait notre template doit faire mention de la propriété minimalTLSVersion avec la bonne valeur pour être valide.
Dans les pros, cela sensibilise plus les devops à la mise en place du tls. Dans les contres, cela peut casser des chaînes de CI/CD existantes. Cela casse l’expérience développeur pour un champ qu’il n’avait pas juger important de mettre dans un contexte sécurisé par Azure Policy.
Si l’on reste sur notre première version de notre policy, il est possible de rajouter une policy Append qui va s’exécuter avant notre Deny
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.EventHub/namespaces"
},
{
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"exists": false
}
]
},
"then": {
"effect": "append",
"details": [
{
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"value": "1.2"
}
]
}
}
Dans les pros, on résout tous les contres de l’implémentation précédente. Dans les contres, a-t-on vraiment besoin de 2 policies pour un simple problème de TLS ? Cet argument est bien entendu un pour si vous êtes payé à la policy.
On va donc supprimer notre Deny policy pour la remplacer par celle ci:
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.EventHub/namespaces"
}
]
},
"then": {
"effect": "modify",
"details": {
"operations": [
{
"operation": "addOrReplace",
"field": "Microsoft.EventHub/namespaces/minimumTlsVersion",
"value": "1.2"
}
],
"roleDefinitionIds": [
"/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"
]
}
}
}
Dans les pros, on modifie la propriété de manière transparente par l’utilisateur, et on n’a qu’une policy à modifier lors d’un passage à TLS 1.3 par exemple. De plus elle a un avantage supplémentaire que nous détaillerons dans un prochain article. Dans les contres, une identité est créé pour vous et assigner à Azure, il faut donc gérer les droits qu’elle a (ici c’est du Contributor par facilité)
Pour moi il n’y a pas de meilleure propostion, c’est à vous de choisir en fonction de vos cas d’usages. Dans ce cas précis, si le TLS n’est pas un sujet, vous pouvez utiliser la dernière proposition pour simplifier votre infra as code. Et ainsi vous affranchir de toutes les modifications de vos différentes stacks d’infra as code. Et si c’est un sujet chez vous car il vous reste des applications utilisant du TLS 1.0 ou 1.1 le mieux est de mettre la première version avec des exeptions dans l’assignement de votre policy.
]]>Cependant en fouillant bien vous allez trouver l’option dans les paramètres du data plane de la base elle même.
Bonne nouvelle par défault, l’option require_secure_transport est à ON et la propriété minimum_tls_version est à TLSV1.2. Maintenant la première action se désactive via le portail Azure, quand à la deuxième les seuls options sont TLS 1.2 ou TLS 1.3, mais vous pourrez utiliser la même procédure pour bloquer.
On va donc ajouter deux policies ici, la première pour regarder si la propriété existe
{
"if": {
"field": "type",
"equals": "Microsoft.DBforPostgreSQL/flexibleServers"
},
"then": {
"effect": "auditIfNotExists",
"details": {
"type": "Microsoft.DBforPostgreSQL/flexibleServers/configurations",
"name": "require_secure_transport",
"existenceCondition": {
"field": "Microsoft.DBForPostgreSql/flexibleServers/configurations/value",
"equals": "ON"
}
}
}
}
Et maintenant on peut aussi faire un Deny en cas de changement:
{
"if": {
"allOf": [
{
"field": "name",
"equals": "require_secure_transport"
},
{
"field": "type",
"equals": "Microsoft.DBforPostgreSQL/flexibleServers/configurations"
},
{
"field": "Microsoft.DBForPostgreSql/flexibleServers/configurations/value",
"equals": "OFF"
}
]
},
"then": {
"effect": "deny"
}
}
Vous pouvez bien entendu appliquer quelque chose de similaire si vous ne voulez que du TLS 1.3
]]>Vous pouvez retrouver les différentes méthodes sur la documentation Microsoft
Maintenant on va essayer de jouer avec ici même.
La première méthode cidrSubnet permet de splitter un CIDR en différent ranges, ce qui peut être très pratique lorsque vous déployer des landing zones standardisés, et que vous ne voulez pas précalculer tous les ranges de vos subnets. En clair dans notre template Bicep, on aura quelque chose de ce style
var cidr = '10.0.0.0/20'
var cidrSubnets = [for i in range(0, 10): cidrSubnet(cidr, 24, i)]
resource virtual_network 'Microsoft.Network/virtualNetworks@2023-04-01'= {
name: 'virtual-network-demo'
location: resourceGroup().location
properties: {
addressSpace: {
addressPrefixes: [
cidr
]
}
}
}
@batchSize(1)
resource subnets 'Microsoft.Network/virtualNetworks/subnets@2023-04-01' = [for (item, index) in cidrSubnets : {
name: 'subnet-${index}'
parent: virtual_network
properties: {
addressPrefix: item
}
}]
Et maintenant chose très utile, lorsque vous configurer des Network Rules sur vos services Azure, vous savez très bien que chaque service à son format. Et notamment PostgreSQL qui ne demande un range, mais qui veut la start IP et la end IP. Et bien grâce à la méthode parseCidr plus besoin de le faire dans votre script qui calcule les paramètres. On peut simplement faire comme cela :
var cidrSubnets = [
'4.175.0.0/16'
'4.180.0.0/16'
'4.210.128.0/17'
'4.231.0.0/17'
'4.245.0.0/17'
'13.69.0.0/17'
'13.73.128.0/18'
'13.73.224.0/21'
'13.80.0.0/15'
'13.88.200.0/21'
'13.93.0.0/17'
]
resource flexServer 'Microsoft.DBforPostgreSQL/flexibleServers@2023-03-01-preview' = {
name: 'flexwwopgs'
location: resourceGroup().location
properties: {
administratorLogin: 'bigchief'
administratorLoginPassword: ''
version: '13'
availabilityZone: '1'
storage: {
storageSizeGB: 32
}
highAvailability: {
mode: 'Disabled'
}
maintenanceWindow: {
customWindow: 'Disabled'
dayOfWeek: 0
startHour: 0
startMinute: 0
}
}
sku: {
name: 'Standard_B1ms'
tier: 'Burstable'
}
}
@batchSize(1)
resource flexServerAcls 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2023-03-01-preview' = [for (item, index) in cidrSubnets: {
name: 'flexpgswwo-${index}'
parent: flexServer
properties: {
startIpAddress: parseCidr(item).firstUsable
endIpAddress: parseCidr(item).lastUsable
}
}]
Donc un grand gain de temps, et cela permet d’éviter les erreurs. De mon côté je suis bien content de voir que Microsoft continu d’investir dans de nouvelles fonctions avec une vraie valeur ajoutée.
]]>Bref, une preview qui s’est fait un peu attendre.
Dans l’article, et sur la documentation Azure, on nous dit qu’il faut migrer de Blueprint à Deployment Stack + Template Specs avant Juillet 2026, donc pas de précipation.
Si on compare les deux très rapidement.
Les blueprints sont un moyen déclaratif d’orchestrer le déploiement de divers modèles de ressources et d’autres artefacts, notamment ceux-ci:
Le cycle de vie d’un Blueprint:
Blueprint est inclus avec une gestion du lock, sous la forme de 3 modes:
Le lock était intégré dans Azure via un Deny Assignment et pas via le type de Lock que vous pouvez gérer en tant qu’utilisateur.
Dans les avantages de blueprint, nous avons donc un système de versionning, et l’utilisation de blueprint est très utile pour une mise en place d’une gouvernance à l’échelle.
Dans les contres, cela gère uniquement de l’ARM, et en plus c’est inclus en tant qu’artefact avec un format custom, donc pas pris en compte par VSCode. Les SDKs lié à blueprint peuvent grandement être améliorer. La gestion des locks est ultra limité, et pour finir c’est toujours en preview….
Maintenant au tour de deployment stack, si on reprend l’article, le rôle de ce dernier:
Si on compare point par point à Blueprint.
On peut déjà supporter ARM ou Bicep en tant qu’infra as code, c’est un mieux mais je sais que beaucoup d’entre vous utilisent Terraform.
Le SDK, bien que ce soit une preview c’est déjà disponible dans AzCLI et AzPowershell. On utilise ici des templates ARM ou Bicep natif, pas de notion d’artefact. Et c’est intégré dans le portail quasiment partout. Par contre plus de notion de versionning….
La gestion des locks, n’a rien à voir maintenant on a tout cela :
Je pense que même si c’est relativement nouveau, Deployment stack a de l’avenir, et couplé à Template Specs cela peut faire un bon remplacement à Blueprint. J’espère que d’ici 1 an ou 2 je pourrais vous faire un retour d’expérience sur une migration depuis Blueprint
]]>Pour mon dernier projet, j’ai décidé de faire cela dans les règles de l’art, enfin du moins j’essaye, et donc j’ai mis en place des tests unitaires pour mon Azure Function. Vu que de mon point de vue faire des mocks pour les accès en base sont inutiles à ce jour, vu la simplicité d’avoir des bases ou émulateurs locals et jetable pour faire les tests, autant les utliser, et ne pas à avoir reproduire tout le mock liés aux accès.
Ici mon besoin est très simple, utiliser une Azure Function qui va insérer des données dans un Table Storage.
Mon code C# pour me connecter à mon storage account est donc le suivant :
var client = new TableClient(new Uri(this._options.Uri), tableName, new DefaultAzureCredential());
Cela me permet d’uniquement de donner l’accès ma table Client, et d’utiliser le DefaultAzureCredential qui me permettra à terme d’utiliser une Managed Identity dans Azure.
Si l’on suit la documentation Azure, ils expliquent qu’il faut mettre comme url pour notre Table Storage quelque chose comme ceci : http://127.0.0.1:10002/. C’est donc à ce moment que je me suis dit mais pourquoi pas https. Si l’on regarde la suite de la documentation ils expliquent comment faire, mais mettons cela sur le compte de la fatigue, ça ne fonctionne pas, donc on va le faire ici pas à pas.
Simplement dans les extensions de Visual Studio Code, chercher Azurite et l’installer. Ou via le lien du Marketplace
Via la documentation Microsoft : Storage Explorer
Via Winget
winget install mkcert
Mais tout autre outil pour générer un certificat pem avec sa clé fera l’affaire.
On va créer notre certificat pour notre Azurite local pour commencer, pour cela on va utlisier mkcert.
mkcert -install
mkcert 127.0.0.1
On a donc deux fichiers qui ont été créés suite à cette commande. Après cela, on va modifier les paramètres de votre VS Code pour configurer notre Azurite de la manière suivante :
"azurite.location": ".azurite",
"azurite.cert": "D:\\Community\\ipam\\.azurite\\127.0.0.1.pem",
"azurite.key": "D:\\Community\\ipam\\.azurite\\127.0.0.1-key.pem",
"azurite.oauth": "basic"
Pour ma part, j’utilise les configurations de mon Workspace, et pas celle globale, car ceci est lié qu’à ce projet. Donc les configurations suivantes servent donc à ceci :
Il ne reste plus qu’à lancer Azurite, et à lancer notre test.
Tester depuis VSCode c’est bien, on a un beau message qui nous X tests réussis. Mais bon je suis curieux et je veux quand même voir le contenu de mon storage.
Pour cela, je réouvre mkcert et j’effectue la commande suivante :
mkcert -CAROOT
Ici je récupère le chemin vers un dossier qui contient pour CAROOT. J’importe celui-ci et celui qui s’appelle 127.0.0.1.pem dans Storage Explorer via l’option “Edit” -> “SSL Certificates” -> “Import Certificate”. Un reboot, et on peut après importer notre Storage Local, et le parcourir comme s’il était sur Azure.
]]>