Dans les coulisses du pare-feu de confidentialité des données

Remarque

Les niveaux de confidentialité ne sont actuellement pas disponibles dans les flux de données Power Platform, mais l’équipe produit travaille à activer cette fonctionnalité.

Si vous avez utilisé Power Query pour n'importe quelle durée, vous avez probablement rencontré ce problème. Là, vous interrogez, quand vous obtenez soudainement une erreur que aucune quantité de recherche en ligne, d’ajustement des requêtes ou de bashing de clavier ne peut résoudre. Une erreur telle que :

Formula.Firewall: Query 'Query1' (step 'Source') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.

Ou peut-être :

Formula.Firewall: Query 'Query1' (step 'Source') is accessing data sources that have privacy levels which cannot be used together. Please rebuild this data combination.

Ces Formula.Firewall erreurs sont le résultat du pare-feu de confidentialité des données de Power Query (également connu sous le nom de pare-feu), qui peut parfois sembler exister uniquement pour frustrer les analystes de données du monde entier. Croyez-le ou non, toutefois, le pare-feu remplit une fonction importante. Nous allons dans cet article fouiller sous le capot pour mieux comprendre son fonctionnement. Armé d’une meilleure compréhension, vous serez en mesure de mieux diagnostiquer et corriger les erreurs de pare-feu à l’avenir.

De quoi s’agit-il ?

L’objectif du pare-feu de confidentialité des données est simple : il existe pour empêcher Power Query d’être à l’origine de fuite de données involontaire entre les sources.

Pourquoi cela est-il nécessaire ? Je veux dire, vous pourriez certainement créer un M qui passerait une valeur SQL à un flux OData. Mais il s’agirait d’une fuite intentionnelle de données. L’auteur mashup saurait (ou au moins devrait savoir) qu’il fait cela. Pourquoi alors le besoin d’une protection contre les fuites de données involontaires ?

La réponse ? Le repli.

Le repli?

Le repli est un terme qui fait référence à la conversion d’expressions en M (tels que des filtres, des renommages, des jointures, etc.) en opérations sur une source de données brute (par exemple, SQL, OData, etc.). Une large partie de la puissance de Power Query provient du fait que PQ peut convertir les opérations qu’un utilisateur effectue via son interface utilisateur en langages SQL complexes ou d’autres langages de source de données back-end, sans que l’utilisateur ait à connaître les langages en question. Les utilisateurs bénéficient des performances des opérations de source de données natives, avec la facilité d’utilisation d’une interface utilisateur où toutes les sources de données peuvent être transformées à l’aide d’un ensemble commun de commandes.

Dans le cadre du repli, PQ peut parfois déterminer que le moyen le plus efficace d’exécuter un mashup donné est de prendre des données d’une source et de les transmettre à une autre. Ainsi, si vous joignez un petit fichier CSV à une table SQL énorme, vous ne souhaitez probablement pas que PQ lise le fichier CSV, lise la table SQL entière, puis les joigne sur votre ordinateur local. Vous souhaitez probablement que PQ intègre les données CSV dans une instruction SQL et demande à la base de données SQL d’effectuer la jointure.

C’est ainsi que les fuites de données involontaires peuvent se produire.

Imaginez que vous joigniez des données SQL qui incluaient des numéros de sécurité sociale des employés avec les résultats d’un flux OData externe et vous découvrez soudainement que les numéros de sécurité sociale de SQL étaient envoyés au service OData. Mauvaises nouvelles, n’est-ce pas ?

Il s’agit du type de scénario que le pare-feu est destiné à empêcher.

Comment cela fonctionne-t-il ?

Le pare-feu existe pour empêcher l’envoi involontaire de données d’une source à une autre source. C’est plutôt simple.

Comment s’y prend-il pour accomplir cette mission ?

Pour ce faire, il divise vos requêtes M en ce que l’on appelle partitions, puis en appliquant la règle suivante :

  • Une partition peut accéder à des sources de données compatibles ou référencer d’autres partitions, mais pas les deux.

Cela paraît simple... pourtant c’est déroutant. C'est quoi une partition ? Qu’est-ce qui fait que deux sources de données sont « compatibles » ? Et pourquoi le pare-feu doit-il se soucier si une partition souhaite accéder à une source de données et référencer une partition ?

Nous allons décomposer cela et examiner la règle ci-dessus morceau par morceau.

C'est quoi une partition ?

Au niveau le plus simple, une partition n’est qu’une collection d’une ou plusieurs étapes de requête. La partition la plus granulaire possible (au moins dans l’implémentation actuelle) est d’une seule étape. Les partitions les plus volumineuses peuvent parfois englober plusieurs requêtes. (Des informations complémentaires vous seront fournies ultérieurement.)

Si vous n’êtes pas familier avec les étapes, vous pouvez les afficher à droite de la fenêtre Éditeur Power Query après avoir sélectionné une requête, dans le volet Étapes appliquées. Les étapes suivent tout ce que vous avez fait pour transformer vos données en leur forme finale.

Partitions qui référencent d’autres partitions

Lorsqu’une requête est évaluée avec le pare-feu activé, le pare-feu divise la requête et toutes ses dépendances en partitions (autrement dit, en groupes d’étapes). Chaque fois qu’une partition fait référence à quelque chose dans une autre partition, le pare-feu remplace la référence par un appel à une fonction spéciale appelée Value.Firewall. directe. Toutes les références sont modifiées pour passer par le pare-feu. Considérez le pare-feu comme un gardien. Une partition qui référence une autre partition doit obtenir l’autorisation du pare-feu pour le faire et le Pare-feu contrôle si les données référencées seront autorisées ou non dans la partition.

Tout cela peut sembler assez abstrait, examinons donc un exemple.

Supposons que vous disposez d’une requête appelée Employés, qui extrait des données d’une base de données SQL. Supposons que vous avez également une autre requête (EmployeesReference), qui fait simplement référence aux employés.

shared Employees = let
    Source = Sql.Database(…),
    EmployeesTable = …
in
    EmployeesTable;

shared EmployeesReference = let
    Source = Employees
in
    Source;

Ces requêtes se divisent en deux partitions : une pour la requête Employees et une pour la requête EmployeesReference (qui fait référence à la partition Employees). Lorsqu’elles sont évaluées avec le pare-feu, ces requêtes sont réécrites comme suit :

shared Employees = let
    Source = Sql.Database(…),
    EmployeesTable = …
in
    EmployeesTable;

shared EmployeesReference = let
    Source = Value.Firewall("Section1/Employees")
in
    Source;

Notez que la simple référence à la requête Employees a été remplacée par un appel à Value.Firewall, qui est fourni le nom complet de la requête Employees.

Lorsque EmployeesReference est évalué, l’appel à Value.Firewall("Section1/Employees") est intercepté par le pare-feu, qui a maintenant la possibilité de contrôler si (et comment) les données demandées sont transmises dans la partition EmployeesReference. Il peut effectuer n’importe quel nombre d’opérations : refuser la demande, mettre en mémoire tampon les données demandées (ce qui empêche tout repli supplémentaire vers sa source de données d’origine) et ainsi de suite.

C’est ainsi que le pare-feu maintient le contrôle des données qui circulent entre les partitions.

Partitions qui accèdent directement aux sources de données

Imaginons que vous définissiez une requête Query1 en une seule étape (notez que cette requête en une seule étape correspond à une partition de pare-feu) et que cette seule étape accède à deux sources de données : une table de base de données SQL et un fichier CSV. Comment le pare-feu gère-t-il cela, puisqu’il n’y a aucune référence de partition et donc aucun appel à Value.Firewall pour qu’il intercepte ? Examinons la règle mentionnée précédemment :

  • Une partition peut accéder à des sources de données compatibles ou référencer d’autres partitions, mais pas les deux.

Pour que votre requête à partition unique mais à deux sources de données soit autorisée à s’exécuter, ses deux sources de données doivent être « compatibles ». En d’autres termes, il doit être acceptable que les données soient partagées de manière bidirectionnelle entre eux. Cela signifie que les niveaux de confidentialité des deux sources doivent être publics, ou qu’elles doivent toutes deux être organisationnelles, car ce sont les deux seules combinaisons qui permettent le partage dans les deux sens. Si les deux sources sont marquées comme étant privées, ou si l’une est marquée comme étant publique et l’autre comme étant organisationnelle, ou si elles sont marquées en utilisant une autre combinaison de niveaux de confidentialité, alors le partage bidirectionnel n’est pas autorisé, et il n’est donc pas sûr que les deux sources soient évaluées dans la même partition. Cela signifierait que des fuites de données non sécurisées peuvent se produire (en raison du repli) et que le pare-feu n’aurait aucun moyen de l’empêcher.

Que se passe-t-il si vous essayez d’accéder à des sources de données incompatibles dans la même partition ?

Formula.Firewall: Query 'Query1' (step 'Source') is accessing data sources that have privacy levels which cannot be used together. Please rebuild this data combination.

Espérons que vous comprenez maintenant mieux l’un des messages d’erreur répertoriés au début de cet article.

Notez que cette exigence de compatibilité s’applique uniquement dans une partition donnée. Si une partition fait référence à d’autres partitions, les sources de données des partitions référencées n’ont pas besoin d’être compatibles les unes avec les autres. Cela est dû au fait que le pare-feu peut mettre en mémoire tampon les données, ce qui empêche tout repli supplémentaire sur la source de données d’origine. Les données seront chargées en mémoire et traitées comme si elles provenaient de nulle part.

Pourquoi ne pas faire les deux ?

Supposons que vous définissez une requête avec une étape (qui correspond à nouveau à une partition) qui accède à deux autres requêtes (c’est-à-dire deux autres partitions). Que se passe-t-il si vous souhaitez, dans la même étape, accéder directement à une base de données SQL ? Pourquoi une partition ne peut-elle pas référencer d’autres partitions et accéder directement aux sources de données compatibles ?

Comme vous l’avez vu précédemment, quand une partition fait référence à une autre partition, le pare-feu agit comme le gardien de toutes les données qui circulent dans la partition. Pour ce faire, il doit être en mesure de contrôler les données autorisées. Si des sources de données sont accessibles au sein de la partition et que des données proviennent d’autres partitions, il perd sa capacité à être le gatekeeper, car les données entrantes pourraient être divulguées à l’une des sources de données accessibles en interne sans qu’il le sache. Par conséquent, le pare-feu empêche une partition qui accède à d’autres partitions d’être autorisée à accéder directement à des sources de données.

Que se passe-t-il si une partition tente de référencer d’autres partitions et d’accéder directement aux sources de données ?

Formula.Firewall: Query 'Query1' (step 'Source') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.

Maintenant, vous devriez mieux comprendre l’autre message d’erreur répertorié au début de cet article.

Partitions en profondeur

Comme vous pouvez probablement deviner à partir des informations ci-dessus, la façon dont les requêtes sont partitionnée finit par être incroyablement importante. Si vous avez des étapes qui font référence à d’autres requêtes et d’autres étapes qui accèdent aux sources de données, vous reconnaissez maintenant que dessiner les limites de partition à certains endroits entraînera des erreurs de pare-feu, tandis que leur dessin dans d’autres emplacements permettra à votre requête de s’exécuter correctement.

Comment les requêtes sont-elles partitionnée ?

Cette section est probablement la plus importante pour comprendre pourquoi vous voyez des erreurs de pare-feu et comment les résoudre (dans la mesure du possible).

Voici un résumé général de la logique de partitionnement.

  • Initial Partitioning
    • Crée une partition pour chaque étape de chaque requête
  • Phase statique
    • Cette phase ne dépend pas des résultats d’évaluation. Plutôt, elle s’appuie sur la façon dont les requêtes sont structurées.
      • Réglage des paramètres
        • Règle les paramètres des partitions, c’est-à-dire toutes celles qui :
          • Ne référence aucune autre partition
          • Ne contient aucun appel de fonction
          • N’est pas cyclique (autrement dit, il ne fait pas référence à lui-même)
        • Notez que la « suppression » d’une partition l’inclut dans les autres partitions qui le référencent.
        • Les partitions de paramètres de réglage permettent aux références de paramètres utilisées dans les appels de fonction de source de données (par exemple Web.Contents(myUrl)) de fonctionner, au lieu de générer des erreurs comme « la partition ne peut pas référencer les sources de données et d’autres étapes ».
      • Regroupement (statique)
        • Les partitions sont fusionnées dans l’ordre de dépendance allant du bas vers le haut. Dans les partitions fusionnées résultantes, les éléments suivants sont séparés :
          • Partitions dans différentes requêtes
          • Les partitions qui ne font pas référence à d’autres partitions (et sont donc autorisées à accéder à une source de données)
          • Les partitions qui référencent d’autres partitions (et sont donc interdites d’accéder à une source de données)
  • Phase dynamique
    • Cette phase dépend des résultats d’évaluation, notamment des informations sur les sources de données accessibles par différentes partitions.
    • Réglage
      • Régler les partitions qui répondent à l'ensemble des exigences suivantes :
        • N’accède à aucune source de données
        • Ne référence aucune partition qui accède aux sources de données
        • N’est pas cyclique
    • Regroupement (Dynamique)
      • Maintenant que les partitions inutiles ont été supprimées, essayez de créer des partitions sources aussi volumineuses que possible. Pour ce faire, fusionnez les partitions à l’aide des mêmes règles décrites dans la phase de regroupement statique ci-dessus.

Qu'est-ce que tout cela signifie ?

Examinons un exemple pour illustrer le fonctionnement de la logique complexe décrite ci-dessus.

Voici un exemple de scénario. Il s’agit d’une fusion assez simple d’un fichier texte (Contacts) avec une base de données SQL (Employés), où le serveur SQL est un paramètre (DbServer).

Les trois requêtes

Voici le code M pour les trois requêtes utilisées dans cet exemple.

shared DbServer = "montegoref6" meta [IsParameterQuery=true, Type="Text", IsParameterQueryRequired=true];
shared Contacts = let

    Source = Csv.Document(File.Contents("C:\contacts.txt"),[Delimiter="   ", Columns=15, Encoding=1252, QuoteStyle=QuoteStyle.None]),

    #"Promoted Headers" = Table.PromoteHeaders(Source, [PromoteAllScalars=true]),

    #"Changed Type" = Table.TransformColumnTypes(#"Promoted Headers",{{"ContactID", Int64.Type}, {"NameStyle", type logical}, {"Title", type text}, {"FirstName", type text}, {"MiddleName", type text}, {"LastName", type text}, {"Suffix", type text}, {"EmailAddress", type text}, {"EmailPromotion", Int64.Type}, {"Phone", type text}, {"PasswordHash", type text}, {"PasswordSalt", type text}, {"AdditionalContactInfo", type text}, {"rowguid", type text}, {"ModifiedDate", type datetime}})

in

    #"Changed Type";
shared Employees = let

    Source = Sql.Databases(DbServer),

    AdventureWorks = Source{[Name="AdventureWorks"]}[Data],

    HumanResources_Employee = AdventureWorks{[Schema="HumanResources",Item="Employee"]}[Data],

    #"Removed Columns" = Table.RemoveColumns(HumanResources_Employee,{"HumanResources.Employee(EmployeeID)", "HumanResources.Employee(ManagerID)", "HumanResources.EmployeeAddress", "HumanResources.EmployeeDepartmentHistory", "HumanResources.EmployeePayHistory", "HumanResources.JobCandidate", "Person.Contact", "Purchasing.PurchaseOrderHeader", "Sales.SalesPerson"}),

    #"Merged Queries" = Table.NestedJoin(#"Removed Columns",{"ContactID"},Contacts,{"ContactID"},"Contacts",JoinKind.LeftOuter),

    #"Expanded Contacts" = Table.ExpandTableColumn(#"Merged Queries", "Contacts", {"EmailAddress"}, {"EmailAddress"})

in

    #"Expanded Contacts";

Voici une vue de niveau supérieur, montrant les dépendances.

Query Dependencies Dialog.

Commençons à partitionner

Nous allons effectuer un zoom avant et inclure des étapes dans l’image, puis commencer à parcourir la logique de partitionnement. Voici un diagramme des trois requêtes, montrant les partitions de pare-feu initiales en vert. Notez que chaque étape démarre dans sa propre partition.

Initial firewall partitions.

Ensuite, nous supprimons les partitions paramétrées. Par conséquent, DbServer est implicitement inclus dans la partition source.

Trimmed firewall partitions.

Maintenant, nous effectuons le regroupement statique. Cela maintient la séparation entre les partitions dans des requêtes distinctes (notez par exemple que les deux dernières étapes des Employés ne sont pas regroupées avec les étapes de Contacts), et entre les partitions qui référencent d’autres partitions (telles que les deux dernières étapes des Employés) et celles qui ne le font pas (telles que les trois premières étapes des Employés).

Post static-grouping firewall partitions.

Maintenant, nous entrons dans la phase dynamique. Dans cette phase, les partitions statiques ci-dessus sont évaluées. Les partitions qui n’accèdent à aucune source de données sont supprimées. Les partitions sont ensuite regroupées pour créer des partitions sources aussi volumineuses que possible. Toutefois, dans cet exemple de scénario, toutes les partitions restantes accèdent aux sources de données et il n’existe aucun regroupement supplémentaire qui peut être effectué. Les partitions de notre exemple ne changent donc pas pendant cette phase.

Nous allons faire semblant

À titre d’illustration, cependant, examinons ce qui se passerait si, au lieu de provenir d’un fichier texte, la requête Contacts était codée en dur dans M (peut-être via la boîte de dialogue Saisir des données).

Dans ce cas, la requête Contacts n’accéderait à aucune source de données. Ainsi, elle serait supprimée pendant la première partie de la phase dynamique.

Firewall partition after dynamic phase trimming.

Avec la partition Contacts supprimée, les deux dernières étapes des Employés ne référencent plus aucune partition, sauf celle contenant les trois premières étapes des employés. Ainsi, les deux partitions seraient regroupées.

La partition résultante ressemblerait à ceci.

Final firewall partitions.

Exemple : passage de données d’une source de données à une autre

Bon, assez d’explication abstraite. Examinons un scénario courant où vous êtes susceptible de rencontrer une erreur de pare-feu et les étapes à suivre pour la résoudre.

Imaginez que vous souhaitez rechercher un nom d’entreprise à partir du service Northwind OData, puis utiliser le nom de la société pour effectuer une recherche Bing.

Tout d’abord, vous créez une requête d’entreprise pour récupérer le nom de la société.

let
    Source = OData.Feed("https://services.odata.org/V4/Northwind/Northwind.svc/", null, [Implementation="2.0"]),
    Customers_table = Source{[Name="Customers",Signature="table"]}[Data],
    CHOPS = Customers_table{[CustomerID="CHOPS"]}[CompanyName]
in
    CHOPS

Ensuite, vous créez une requête Recherche qui référence l’entreprise et la transmet à Bing.

let
    Source = Text.FromBinary(Web.Contents("https://www.bing.com/search?q=" & Company))
in
    Source

À ce stade, vous rencontrez des problèmes. L’évaluation de la Recherche génère une erreur de pare-feu.

Formula.Firewall: Query 'Search' (step 'Source') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.

Cela est dû au fait que l’étape source de Recherche fait référence à une source de données (bing.com) et fait également référence à une autre requête/partition (Société). Cela viole la règle mentionnée ci-dessus (« une partition peut soit accéder à des sources de données compatibles, soit faire référence à d’autres partitions, mais pas les deux »).

Que faire, alors ? Une option consiste à désactiver complètement le pare-feu (via l’option Confidentialité étiquetée Ignorer les niveaux de confidentialité et potentiellement améliorer les performances). Mais que se passe-t-il si vous voulez laisser le pare-feu activé ?

Pour résoudre l’erreur sans désactiver le pare-feu, vous pouvez combiner l’entreprise et la recherche dans une seule requête, comme ceci :

let
    Source = OData.Feed("https://services.odata.org/V4/Northwind/Northwind.svc/", null, [Implementation="2.0"]),
    Customers_table = Source{[Name="Customers",Signature="table"]}[Data],
    CHOPS = Customers_table{[CustomerID="CHOPS"]}[CompanyName],
    Search = Text.FromBinary(Web.Contents("https://www.bing.com/search?q=" & CHOPS))
in
    Search

Tout se passe maintenant à l’intérieur d’une seule partition. En supposant que les niveaux de confidentialité des deux sources de données sont compatibles, le pare-feu doit maintenant être satisfait et vous n’obtiendrez plus d’erreur.

C’est terminé

Bien qu’il y ait beaucoup plus à dire sur ce sujet, cet article d’introduction est déjà assez long. Nous espérons que cela vous a permis de mieux comprendre le pare-feu et vous aidera à comprendre et à corriger les erreurs de pare-feu lorsque vous les rencontrez à l’avenir.