Bag kulisserne i Firewall til beskyttelse af personlige oplysninger

Bemærk

Niveauer for beskyttelse af personlige oplysninger er i øjeblikket ikke tilgængelige i Power Platform-dataflow, men produktteamet arbejder på at aktivere denne funktionalitet.

Hvis du har brugt Power Query i et stykke tid, har du sandsynligvis oplevet det. Der er du, forespørger væk, når du pludselig får en fejl, at ingen mængde af onlinesøgning, forespørgsel tweaking, eller tastatur bashing kan afhjælpe. En fejl som f.eks.:

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.

Eller måske:

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

Disse Formula.Firewall fejl er resultatet af Power Querys Firewall til beskyttelse af personlige oplysninger (også kendt som Firewall), som til tider kan se ud som om den kun findes for at frustrere dataanalytikere over hele verden. Tro det eller ej, men firewall tjener et vigtigt formål. I denne artikel dykker vi ned under hjelmen for bedre at forstå, hvordan det fungerer. Bevæbnet med større forståelse, vil du forhåbentlig være i stand til bedre at diagnosticere og løse Firewall-fejl i fremtiden.

Hvad er det?

Formålet med Firewall til beskyttelse af personlige oplysninger er enkelt: Den findes for at forhindre, at Power Query utilsigtet lækker data mellem kilder.

Hvorfor er det nødvendigt? Jeg mener, du kunne helt sikkert forfatter nogle M, der ville videregive en SQL værdi til en OData feed. Men dette ville være bevidst datalækage. Den miks forfatter ville (eller i det mindste bør) vide, at de gjorde dette. Hvorfor så behovet for beskyttelse mod utilsigtet datalækage?

Svaret? Folde.

Folde?

Foldning er et ord, der refererer til konvertering af udtryk i M (f.eks. filtre, omdøbninger, joinforbindelser osv.) til handlinger i forhold til en rå datakilde (f.eks. SQL, OData osv.). En stor del af Power Querys styrke stammer fra det faktum, at PQ kan konvertere de handlinger, en bruger udfører via sin brugergrænseflade, til komplekse SQL- eller andre backend-datakildesprog, uden at brugeren behøver at kende disse sprog. Brugerne får fordel af ydeevnen ved oprindelige datakildehandlinger med brugervenligheden af en brugergrænseflade, hvor alle datakilder kan transformeres ved hjælp af et fælles sæt kommandoer.

Som en del af foldning kan PQ nogle gange bestemme, at den mest effektive måde at udføre et givent miks på er at tage data fra én kilde og overføre dem til en anden. Hvis du f.eks. forbinder en lille CSV-fil til en enorm SQL-tabel, vil du sandsynligvis ikke have, at PQ læser CSV-filen, læser hele SQL-tabellen og derefter slutter dem sammen på din lokale computer. Du vil sandsynligvis gerne have, at PQ indsætter CSV-dataene i en SQL-sætning og beder SQL-databasen om at udføre joinforbindelsen.

Sådan kan utilsigtet datalækage ske.

Forestil dig, at du tilmelder dig SQL-data, der inkluderede medarbejder cpr-numre med resultaterne af et eksternt OData-feed, og du pludselig opdagede, at CPR-numre fra SQL blev sendt til OData-tjenesten. Dårlige nyheder, ikke?

Dette er den type scenarie, som Firewall er beregnet til at forhindre.

Hvordan fungerer det?

Firewallen findes for at forhindre, at data fra én kilde utilsigtet sendes til en anden kilde. Simpelt nok.

Så hvordan kan det opnå denne mission?

Det gør den ved at opdele dine M-forespørgsler i noget, der kaldes partitioner, og derefter gennemtvinge følgende regel:

  • En partition kan enten få adgang til kompatible datakilder eller referere til andre partitioner, men ikke begge dele.

Simpel... men forvirrende. Hvad er en partition? Hvad gør to datakilder "kompatible"? Og hvorfor skal firewallen være forsigtig med, om en partition ønsker at få adgang til en datakilde og referere til en partition?

Lad os opdele dette og se på ovenstående regel ét stykke ad gangen.

Hvad er en partition?

På det mest grundlæggende niveau er en partition blot en samling af et eller flere forespørgselstrin. Den mest detaljerede partition (i det mindste i den aktuelle implementering) er et enkelt trin. De største partitioner kan nogle gange omfatte flere forespørgsler. (Mere om dette senere.)

Hvis du ikke kender trinnene, kan du få dem vist til højre for vinduet Power Query-editor, når du har valgt en forespørgsel, i ruden Anvendte trin. Trinene holder styr på alt, hvad du har gjort for at transformere dine data til deres endelige form.

Partitioner, der refererer til andre partitioner

Når en forespørgsel evalueres med Firewall slået til, opdeler Firewall forespørgslen og alle dens afhængigheder i partitioner (dvs. grupper af trin). Når en partition refererer til noget i en anden partition, erstatter firewallen referencen med et kald til en særlig funktion kaldet Value.Firewall. Firewallen tillader med andre ord ikke, at partitioner har direkte adgang til hinanden. Alle referencer ændres, så de går gennem firewallen. Tænk på Firewall som portvogter. En partition, der refererer til en anden partition, skal have firewallens tilladelse til at gøre det, og Firewall styrer, om de data, der refereres til, er tilladt i partitionen.

Alt dette kan virke temmelig abstrakt, så lad os se på et eksempel.

Antag, at du har en forespørgsel med navnet Medarbejdere, som henter nogle data fra en SQL-database. Antag, at du også har en anden forespørgsel (EmployeesReference), som blot refererer til Medarbejdere.

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

shared EmployeesReference = let
    Source = Employees
in
    Source;

Disse forespørgsler vil ende med at blive opdelt i to partitioner: én for forespørgslen Medarbejdere og én for forespørgslen EmployeesReference (som refererer til partitionen Medarbejdere). Når disse forespørgsler evalueres med Firewall slået til, bliver de omskrevet på følgende måde:

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

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

Bemærk, at den enkle reference til forespørgslen Medarbejdere er blevet erstattet af et kald til Value.Firewall, som er angivet det fulde navn på forespørgslen Medarbejdere.

Når EmployeesReference evalueres, opfanges opkaldet til Value.Firewall("Section1/Employees") af Firewall, som nu har mulighed for at styre, om (og hvordan) de ønskede data flyder ind i partitionen EmployeesReference. Den kan gøre et vilkårligt antal ting: afvise anmodningen, gemme de ønskede data i buffer (hvilket forhindrer yderligere foldning til den oprindelige datakilde) osv.

Det er sådan, firewallen bevarer kontrollen over de data, der flyder mellem partitioner.

Partitioner, der har direkte adgang til datakilder

Lad os sige, at du definerer en forespørgselsforespørgsel1 med ét trin (bemærk, at denne forespørgsel med ét trin svarer til én firewallpartition), og at dette enkelte trin har adgang til to datakilder: en SQL-databasetabel og en CSV-fil. Hvordan firewall håndtere dette, da der ikke er nogen partition reference, og dermed ingen opfordring til for Value.Firewall den at opfange? Lad os gennemgå den tidligere angivne regel:

  • En partition kan enten få adgang til kompatible datakilder eller referere til andre partitioner, men ikke begge dele.

Hvis forespørgslen med en enkelt partition, men to datakilder skal have tilladelse til at køre, skal dens to datakilder være "kompatible". Det skal med andre ord være i orden, hvis data skal deles tovejs mellem dem. Det betyder, at niveauerne for beskyttelse af personlige oplysninger for begge kilder skal være offentlige eller begge være organisatoriske, da disse er de eneste to kombinationer, der tillader deling i begge retninger. Hvis begge kilder er markeret som Private, eller en er markeret som Offentlig, og én er markeret som Organisatorisk, eller hvis de er markeret med en anden kombination af niveauer for beskyttelse af personlige oplysninger, er tovejsdeling ikke tilladt, og det er derfor ikke sikkert for dem begge at blive evalueret i den samme partition. Hvis du gør det, kan der opstå usikker datalækage (pga. foldning), og firewallen kan ikke forhindre det.

Hvad sker der, hvis du forsøger at få adgang til inkompatible datakilder i den samme 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.

Forhåbentlig vil du nu bedre forstå en af de fejlmeddelelser, der er angivet i begyndelsen af denne artikel.

Bemærk, at dette kompatibilitetskrav kun gælder inden for en given partition. Hvis en partition refererer til andre partitioner, behøver datakilderne fra de partitioner, der refereres til, ikke at være kompatible med hinanden. Det skyldes, at firewallen kan bufferlagre dataene, hvilket forhindrer yderligere foldning mod den oprindelige datakilde. Dataene indlæses i hukommelsen og behandles, som om de ikke kom fra nogen steder.

Hvorfor ikke gøre begge dele?

Lad os sige, at du definerer en forespørgsel med ét trin (som igen svarer til én partition), der får adgang til to andre forespørgsler (dvs. to andre partitioner). Hvad nu, hvis du på samme trin også ville have direkte adgang til en SQL-database? Hvorfor kan en partition ikke referere til andre partitioner og få direkte adgang til kompatible datakilder?

Som du så tidligere, når en partition refererer til en anden partition, fungerer Firewall som portvogter for alle de data, der flyder ind i partitionen. Hvis du vil gøre det, skal den kunne styre, hvilke data der er tilladt i. Hvis der er datakilder, der tilgås i partitionen, og data, der flyder ind fra andre partitioner, mister det muligheden for at være portvogter, da de data, der flyder ind, kan blive lækket til en af de datakilder, der er internt adgang til, uden at den ved det. Firewallen forhindrer derfor, at en partition, der har adgang til andre partitioner, får direkte adgang til datakilder.

Så hvad sker der, hvis en partition forsøger at referere til andre partitioner og også har direkte adgang til datakilder?

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.

Nu kan du forhåbentlig bedre forstå den anden fejlmeddelelse, der er angivet i starten af denne artikel.

Dybdegående partitioner

Som du sandsynligvis kan gætte fra ovenstående oplysninger, ender det med, at det er utroligt vigtigt, hvordan forespørgsler partitioneres. Hvis du har nogle trin, der refererer til andre forespørgsler og andre trin, der har adgang til datakilder, kan du nu forhåbentlig genkende, at hvis du tegner partitionsgrænserne visse steder, vil det medføre firewallfejl, mens du tegner dem andre steder, så din forespørgsel kan køre fint.

Så hvordan bliver forespørgsler partitioneret?

Dette afsnit er sandsynligvis det vigtigste for at forstå, hvorfor du får vist firewallfejl, og forstå, hvordan du løser dem (hvor det er muligt).

Her er en oversigt på højt niveau over partitionslogikken.

  • Indledende partitionering
    • Opretter en partition for hvert trin i hver forespørgsel
  • Statisk fase
    • Denne fase afhænger ikke af evalueringsresultater. Den er i stedet afhængig af, hvordan forespørgslerne er struktureret.
      • Justering af parameter
        • Trimmer parameter-esque-partitioner, det vil altså enhver, der:
          • Refererer ikke til andre partitioner
          • Indeholder ingen funktionsaktiveringer
          • Er ikke cyklisk (dvs. den refererer ikke til sig selv)
        • Bemærk, at "fjernelse" af en partition effektivt inkluderer den i alle andre partitioner, der refererer til den.
        • Trimning af parameterpartitioner gør det muligt at bruge parameterreferencer, der bruges i kald til datakildefunktioner (f.eks. Web.Contents(myUrl)) i stedet for at aktivere fejl i "partitionen kan ikke referere til datakilder og andre trin".
      • Gruppering (statisk)
        • Partitioner flettes i bunden af afhængighedsrækkefølgen. I de resulterende flettede partitioner vil følgende være separate:
          • Partitioner i forskellige forespørgsler
          • Partitioner, der ikke refererer til andre partitioner (og dermed har tilladelse til at få adgang til en datakilde)
          • Partitioner, der refererer til andre partitioner (og derfor ikke kan få adgang til en datakilde)
  • Dynamisk fase
    • Denne fase afhænger af evalueringsresultater, herunder oplysninger om datakilder, som forskellige partitioner har adgang til.
    • Trimning
      • Trimmer partitioner, der opfylder alle følgende krav:
        • Har ikke adgang til nogen datakilder
        • Refererer ikke til partitioner, der har adgang til datakilder
        • Er ikke cyklisk
    • Gruppering (dynamisk)
      • Nu, hvor unødvendige partitioner er blevet trimmet, kan du prøve at oprette kildepartitioner, der er så store som muligt. Dette gøres ved at flette partitionerne ved hjælp af de samme regler, der er beskrevet i den statiske grupperingsfase ovenfor.

Hvad betyder alt dette?

Lad os gennemgå et eksempel for at illustrere, hvordan den komplekse logik, der er beskrevet ovenfor, fungerer.

Her er et eksempelscenarie. Det er en ret enkel fletning af en tekstfil (Kontakter) med en SQL-database (Medarbejdere), hvor SQL-serveren er en parameter (DbServer).

De tre forespørgsler

Her er M-koden for de tre forespørgsler, der bruges i dette eksempel.

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";

Her er en visning på et højere niveau, der viser afhængighederne.

Dialogboksen Forespørgselsafhængigheder.

Lad os partitionere

Lad os zoome lidt ind og inkludere trin i billedet og begynde at gennemgå partitionslogikken. Her er et diagram over de tre forespørgsler, der viser de indledende firewallpartitioner med grønt. Bemærk, at hvert trin starter i sin egen partition.

Indledende firewallpartitioner.

Derefter trimmer vi parameterpartitioner. DbServer medtages således implicit i kildepartitionen.

Trimmede firewallpartitioner.

Nu udfører vi den statiske gruppering. Dette bevarer adskillelsen mellem partitioner i separate forespørgsler (bemærk f.eks., at de sidste to trin i Medarbejdere ikke grupperes med trinnene i Kontakter) og mellem partitioner, der refererer til andre partitioner (f.eks. de sidste to trin i Medarbejdere) og dem, der ikke gør det (f.eks. de første tre trin i Medarbejdere).

Efter statisk gruppering af firewallpartitioner.

Nu går vi ind i den dynamiske fase. I denne fase evalueres ovenstående statiske partitioner. Partitioner, der ikke har adgang til nogen datakilder, beskæres. Partitioner grupperes derefter for at oprette kildepartitioner, der er så store som muligt. Men i dette eksempelscenarie får alle de resterende partitioner adgang til datakilder, og der er ikke yderligere gruppering, der kan udføres. Partitionerne i vores eksempel ændres derfor ikke i denne fase.

Lad os lade som om

Lad os dog af illustrationens skyld se på, hvad der ville ske, hvis forespørgslen Kontakter i stedet for at komme fra en tekstfil blev hard-coded i M (måske via dialogboksen Angiv data ).

I dette tilfælde ville forespørgslen Kontakter ikke få adgang til nogen datakilder. Det ville således blive trimmet i den første del af den dynamiske fase.

Firewallpartition efter justering af dynamisk fase.

Når partitionen Kontakter er fjernet, vil de sidste to trin i Medarbejdere ikke længere referere til nogen partitioner undtagen den, der indeholder de første tre trin i Medarbejdere. Derfor grupperes de to partitioner.

Den resulterende partition vil se sådan ud.

Endelige firewallpartitioner.

Eksempel: Overførsel af data fra én datakilde til en anden

Okay, nok abstrakt forklaring. Lad os se på et almindeligt scenarie, hvor du sandsynligvis vil støde på en Firewall-fejl og trinnene til at løse den.

Forestil dig, at du vil søge efter et firmanavn fra OData-tjenesten Northwind og derefter bruge firmanavnet til at udføre en Bing-søgning.

Først skal du oprette en firmaforespørgsel for at hente firmanavnet.

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

Derefter skal du oprette en søgeforespørgsel , der refererer til Firma og sender den til Bing.

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

På dette tidspunkt støder du på problemer. Evaluering af søgning resulterer i en firewallfejl.

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.

Det skyldes, at kildetrinnet i Søgefunktionen refererer til en datakilde (bing.com) og også refererer til en anden forespørgsel/partition (Company). Det overtræder ovenstående regel ("en partition kan enten få adgang til kompatible datakilder eller referere til andre partitioner, men ikke begge dele").

Hvad skal du gøre? En mulighed er at deaktivere Firewall helt (via indstillingen Beskyttelse af personlige oplysninger med navnet Ignorer niveauer for beskyttelse af personlige oplysninger og muligvis forbedre ydeevnen). Men hvad nu, hvis du vil lade Firewall være aktiveret?

Hvis du vil løse fejlen uden at deaktivere firewallen, kan du kombinere Firma og Søg i en enkelt forespørgsel, f.eks.:

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

Alt sker nu i en enkelt partition. Hvis det antages, at niveauerne for beskyttelse af personlige oplysninger for de to datakilder er kompatible, bør Firewall nu være glad, og du får ikke længere vist en fejl.

Det er en wrap

Selvom der er meget mere, der kan siges om dette emne, er denne introduktionsartikel allerede lang nok. Forhåbentlig får du en bedre forståelse af Firewall, og det hjælper dig med at forstå og løse firewallfejl, når du støder på dem i fremtiden.