Bak kulissene i brannmuren for personvern

Merk

Personvernnivåer er for øyeblikket utilgjengelige i Power Platform-dataflyter, men produktteamet jobber med å aktivere denne funksjonaliteten.

Hvis du har brukt Power Query over lengre tid, har du sannsynligvis opplevd det. Der er du, spørre bort, når du plutselig får en feil som ingen mengde online søk, spørring tilpasning, eller tastatur bashing kan rette opp. En feil som:

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 kanskje:

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 feilene er resultatet av Power Querys brannmur for datavern (også kjent som brannmuren), som til tider kan virke som om den bare eksisterer for å frustrere dataanalytikere over hele verden. Tro det eller ei, men brannmuren tjener et viktig formål. I denne artikkelen vil vi fordype oss under panseret for bedre å forstå hvordan det fungerer. Bevæpnet med større forståelse, vil du forhåpentligvis kunne diagnostisere og løse brannmurfeil i fremtiden.

Hva er det?

Formålet med datavernbrannmuren er enkel: Den finnes for å hindre at Power Query utilsiktet lekker data mellom kilder.

Hvorfor er dette nødvendig? Jeg mener, du kan sikkert skrive noen M som ville sende en SQL-verdi til en OData-feed. Men dette ville være bevisst datalekkasje. Mashup-forfatteren ville (eller i det minste burde) vite at de gjorde dette. Hvorfor da behovet for beskyttelse mot utilsiktet datalekkasje?

Svaret? Folding.

Folding?

Folding er en term som refererer til konvertering av uttrykk i M (for eksempel filtre, gi nytt navn til, sammenføyninger og så videre) til operasjoner mot en rå datakilde (for eksempel SQL, OData og så videre). En stor del av Power Querys kraft kommer fra det faktum at PQ kan konvertere operasjonene en bruker utfører via brukergrensesnittet til komplekse SQL- eller andre serverdeldatakildespråk, uten at brukeren må vite nevnte språk. Brukere får ytelsesfordelen ved opprinnelige datakildeoperasjoner, med brukervennligheten til et brukergrensesnitt der alle datakilder kan transformeres ved hjelp av et felles sett med kommandoer.

Som en del av foldingen kan PQ noen ganger fastslå at den mest effektive måten å utføre en gitt mashup på, er å ta data fra én kilde og sende den til en annen. Hvis du for eksempel kobler en liten CSV-fil til en stor SQL-tabell, vil du sannsynligvis ikke at PQ skal lese CSV-filen, lese hele SQL-tabellen og deretter koble dem sammen på den lokale datamaskinen. Du vil sannsynligvis at PQ skal innebygde CSV-data i en SQL-setning og be SQL-databasen om å utføre sammenføyningen.

Slik kan utilsiktet datalekkasje skje.

Tenk om du ble med i SQL-data som inkluderte personnummer for ansatte med resultatene av en ekstern OData-feed, og du oppdaget plutselig at personnummeret fra SQL ble sendt til OData-tjenesten. Dårlige nyheter, ikke sant?

Dette er den typen scenario brannmuren er ment å forhindre.

Hvordan fungerer det?

Brannmuren finnes for å hindre at data fra én kilde utilsiktet sendes til en annen kilde. Enkelt nok.

Så hvordan utfører det dette oppdraget?

Det gjør dette ved å dele M-spørringene inn i noe som kalles partisjoner, og deretter håndheve følgende regel:

  • En partisjon kan enten få tilgang til kompatible datakilder eller referere til andre partisjoner, men ikke begge deler.

Enkel... likevel forvirrende. Hva er en partisjon? Hva gjør to datakilder «kompatible»? Og hvorfor bør brannmuren passe hvis en partisjon ønsker å få tilgang til en datakilde og referere til en partisjon?

La oss bryte dette ned og se på regelen ovenfor ett stykke om gangen.

Hva er en partisjon?

På det mest grunnleggende nivået er en partisjon bare en samling av ett eller flere spørringstrinn. Den mest detaljerte partisjonen som er mulig (i hvert fall i gjeldende implementering) er ett enkelt trinn. De største partisjonene kan noen ganger omfatte flere spørringer. (Mer om dette senere.)

Hvis du ikke er kjent med trinnene, kan du vise dem til høyre for Power Query-redigering-vinduet etter at du har valgt en spørring, i ruten Brukte trinn. Trinn holder oversikt over alt du har gjort for å transformere dataene til den endelige figuren.

Partisjoner som refererer til andre partisjoner

Når en spørring evalueres med brannmuren på, deler brannmuren spørringen og alle avhengighetene i partisjoner (det vil eksempelvis grupper med trinn). Når én partisjon refererer til noe i en annen partisjon, erstatter brannmuren referansen med et kall til en spesiell funksjon kalt Value.Firewall. Brannmuren tillater med andre ord ikke partisjoner å få tilgang direkte til hverandre. Alle referanser endres for å gå gjennom brannmuren. Tenk på brannmuren som portvokter. En partisjon som refererer til en annen partisjon, må få brannmurens tillatelse til å gjøre dette, og brannmuren kontrollerer om de refererte dataene skal tillates inn i partisjonen.

Alt dette kan virke ganske abstrakt, så la oss se på et eksempel.

Anta at du har en spørring kalt Ansatte, som henter noen data fra en SQL-database. Anta at du også har en annen spørring (EmployeesReference), som bare refererer til ansatte.

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

shared EmployeesReference = let
    Source = Employees
in
    Source;

Disse spørringene vil ende opp delt inn i to partisjoner: én for Ansatte-spørringen og én for EmployeesReference-spørringen (som vil referere til Ansatte-partisjonen). Når de evalueres med brannmuren på, skrives disse spørringene på nytt slik:

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

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

Legg merke til at den enkle referansen til Ansatte-spørringen har blitt erstattet av et kall til Value.Firewall, som er gitt det fullstendige navnet på Ansatte-spørringen.

Når EmployeesReference evalueres, fanges kallet til Value.Firewall("Section1/Employees") opp av brannmuren, som nå har mulighet til å kontrollere om (og hvordan) de forespurte dataflytene til EmployeesReference-partisjonen. Det kan gjøre en rekke ting: avslå forespørselen, bufre de forespurte dataene (som hindrer ytterligere folding til den opprinnelige datakilden fra å skje), og så videre.

Dette er hvordan brannmuren opprettholder kontrollen over dataflyten mellom partisjoner.

Partisjoner som har direkte tilgang til datakilder

La oss si at du definerer en spørringsspørring 1 med ett trinn (vær oppmerksom på at denne enkelttrinnsspørringen tilsvarer én brannmurpartisjon), og at dette enkelttrinnet får tilgang til to datakilder: en SQL-databasetabell og en CSV-fil. Hvordan håndterer brannmuren dette, siden det ikke er noen partisjonsreferanse, og dermed ikke noe kall til å Value.Firewall fange opp? La oss se gjennom regelen som er angitt tidligere:

  • En partisjon kan enten få tilgang til kompatible datakilder eller referere til andre partisjoner, men ikke begge deler.

For at enkeltpartisjons-men-to-datakildespørringen skal kunne kjøres, må de to datakildene være «kompatible». Det må med andre ord være greit at data deles toveis mellom dem. Dette betyr at personvernnivåene for begge kildene må være offentlige, eller begge er organisatoriske, siden dette er de eneste to kombinasjonene som tillater deling i begge retninger. Hvis begge kildene er merket privat, eller én er merket offentlig og én er merket organisatorisk, eller de er merket ved hjelp av en annen kombinasjon av personvernnivåer, er ikke toveis deling tillatt, og det er derfor ikke trygt for dem å evalueres i samme partisjon. Dette ville bety at usikre datalekkasjer kunne oppstå (på grunn av folding), og brannmuren ville ikke ha noen måte å forhindre det på.

Hva skjer hvis du prøver å få tilgang til inkompatible datakilder i samme partisjon?

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åpentligvis forstår du nå en av feilmeldingene som er oppført i begynnelsen av denne artikkelen.

Vær oppmerksom på at dette kompatibilitetskravet bare gjelder innenfor en gitt partisjon. Hvis en partisjon refererer til andre partisjoner, trenger ikke datakildene fra de refererte partisjonene å være kompatible med hverandre. Dette er fordi brannmuren kan bufre dataene, noe som vil hindre ytterligere folding mot den opprinnelige datakilden. Dataene lastes inn i minnet og behandles som om de kom fra ingensteds.

Hvorfor ikke gjøre begge deler?

La oss si at du definerer en spørring med ett trinn (som igjen tilsvarer én partisjon) som får tilgang til to andre spørringer (det vil si to andre partisjoner). Hva om du vil, i samme trinn, også få direkte tilgang til en SQL-database? Hvorfor kan ikke en partisjon referere til andre partisjoner og få direkte tilgang til kompatible datakilder?

Som du så tidligere, når én partisjon refererer til en annen partisjon, fungerer brannmuren som portvokter for alle dataene som flyter inn i partisjonen. Hvis du vil gjøre dette, må det være i stand til å kontrollere hvilke data som er tillatt i. Hvis det er tilgang til datakilder i partisjonen, og data som flyter inn fra andre partisjoner, mister den muligheten til å være portvokteren, siden dataene som flyter inn, kan lekkes til en av de internt tilgjengelige datakildene uten at den vet om det. Brannmuren hindrer derfor at en partisjon som får tilgang til andre partisjoner, får tilgang til datakilder direkte.

Så hva skjer hvis en partisjon prøver å referere til andre partisjoner og også få direkte tilgang 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.

Nå forstår du forhåpentligvis den andre feilmeldingen som er oppført i begynnelsen av denne artikkelen.

Partisjoner i dybden

Som du sikkert kan gjette fra informasjonen ovenfor, blir spørringer partisjonert utrolig viktig. Hvis du har noen trinn som refererer til andre spørringer og andre trinn som får tilgang til datakilder, gjenkjenner du nå forhåpentligvis at det å tegne partisjonsgrensene på bestemte steder vil føre til brannmurfeil, mens det å tegne dem andre steder gjør at spørringen kan kjøre helt fint.

Så hvordan blir spørringer partisjonert?

Denne delen er sannsynligvis det viktigste for å forstå hvorfor du ser brannmurfeil, og forstå hvordan du løser dem (der det er mulig).

Her er et sammendrag på høyt nivå av partisjoneringslogikken.

  • Første partisjonering
    • Oppretter en partisjon for hvert trinn i hver spørring
  • Statisk fase
    • Denne fasen avhenger ikke av evalueringsresultatene. I stedet er det avhengig av hvordan spørringene er strukturert.
      • Parametertrimming
        • Trimmer parameter-esque partisjoner, det vil eksempel:
          • Refererer ikke til andre partisjoner
          • Inneholder ingen funksjonsinvokasjoner
          • Er ikke syklisk (det vil skyldes at det ikke refererer til seg selv)
        • Vær oppmerksom på at "fjerne" en partisjon effektivt inkluderer den i alle andre partisjoner som refererer til den.
        • Trimming av parameterpartisjoner gjør at parameterreferanser som brukes i datakildefunksjonskall (for eksempel Web.Contents(myUrl)) fungerer, i stedet for å kaste «partisjon kan ikke referere til datakilder og andre trinn»-feil.
      • Gruppering (statisk)
        • Partisjoner slås sammen i avhengighetsrekkefølge nedenfra og opp. I de resulterende sammenslåtte partisjonene vil følgende være atskilt:
          • Partisjoner i ulike spørringer
          • Partisjoner som ikke refererer til andre partisjoner (og som dermed har tilgang til en datakilde)
          • Partisjoner som refererer til andre partisjoner (og som dermed ikke har tilgang til en datakilde)
  • Dynamisk fase
    • Denne fasen avhenger av evalueringsresultater, inkludert informasjon om datakilder som åpnes av ulike partisjoner.
    • Trimming
      • Trimmer partisjoner som oppfyller alle følgende krav:
        • Får ikke tilgang til noen datakilder
        • Refererer ikke til partisjoner som får tilgang til datakilder
        • Er ikke syklisk
    • Gruppering (dynamisk)
      • Nå som unødvendige partisjoner er trimmet, kan du prøve å opprette kildepartisjoner som er så store som mulig. Dette gjøres ved å slå sammen partisjonene ved hjelp av de samme reglene som er beskrevet i den statiske grupperingsfasen ovenfor.

Hva betyr alt dette?

La oss gå gjennom et eksempel for å illustrere hvordan den komplekse logikken ovenfor fungerer.

Her er et eksempelscenario. Det er en ganske enkel sammenslåing av en tekstfil (Kontakter) med en SQL-database (Ansatte), der SQL-serveren er en parameter (DbServer).

De tre spørringene

Her er M-koden for de tre spørringene som brukes i dette eksemplet.

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å høyere nivå, som viser avhengighetene.

Dialogboksen Spørringsavhengigheter.

La oss partisjonere

La oss zoome litt inn og inkludere trinn i bildet, og begynne å gå gjennom partisjoneringslogikken. Her er et diagram over de tre spørringene, som viser de første brannmurpartisjonene i grønt. Legg merke til at hvert trinn starter i sin egen partisjon.

Innledende brannmurpartisjoner.

Deretter trimmer vi parameterpartisjoner. Dermed blir DbServer implisitt inkludert i kildepartisjonen.

Trimmede brannmurpartisjoner.

Nå utfører vi den statiske grupperingen. Dette opprettholder fordeling mellom partisjoner i separate spørringer (vær oppmerksom på at de to siste trinnene av ansatte ikke blir gruppert med trinnene i kontakter), og mellom partisjoner som refererer til andre partisjoner (for eksempel de to siste trinnene i ansatte) og de som ikke gjør det (for eksempel de tre første trinnene i ansatte).

Legg inn statisk gruppering av brannmurpartisjoner.

Nå går vi inn i den dynamiske fasen. I denne fasen evalueres de statiske partisjonene ovenfor. Partisjoner som ikke får tilgang til datakilder, trimmes. Partisjoner grupperes deretter for å opprette kildepartisjoner som er så store som mulig. Men i dette eksempelscenariet får alle de gjenværende partisjonene tilgang til datakilder, og det finnes ikke ytterligere gruppering som kan gjøres. Partisjonene i utvalget vårt endres derfor ikke i denne fasen.

La oss late som

La oss imidlertid se på hva som ville skje hvis kontaktspørringen, i stedet for å komme fra en tekstfil, var hardkodet i M (kanskje via dialogboksen Skriv inn data ).

I dette tilfellet får ikke kontaktspørringen tilgang til noen datakilder. Dermed ville det bli trimmet i den første delen av den dynamiske fasen.

Brannmurpartisjon etter dynamisk fasetrimming.

Når Kontakter-partisjonen er fjernet, vil de to siste trinnene av ansatte ikke lenger referere til noen partisjoner, bortsett fra den som inneholder de tre første trinnene i Ansatte. Dermed grupperes de to partisjonene.

Den resulterende partisjonen vil se slik ut.

Endelige brannmurpartisjoner.

Eksempel: Sende data fra én datakilde til en annen

Ok, nok abstrakt forklaring. La oss se på et vanlig scenario der det er sannsynlig at du støter på en brannmurfeil og trinnene for å løse det.

Tenk deg at du vil slå opp et firmanavn fra Northwind OData-tjenesten, og deretter bruke firmanavnet til å utføre et Bing-søk.

Først oppretter du en firmaspørring for å 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

Deretter oppretter du en søkespørring som refererer til Firmaet og sender den til Bing.

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

På dette tidspunktet støter du på problemer. Evaluering av søk gir en brannmurfeil.

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.

Dette er fordi kildetrinnet for søk refererer til en datakilde (bing.com) og refererer også til en annen spørring/partisjon (firma). Det bryter med regelen nevnt ovenfor ("en partisjon kan enten få tilgang til kompatible datakilder eller referere til andre partisjoner, men ikke begge deler").

Hva gjør du? Ett alternativ er å deaktivere brannmuren helt (via personvernalternativet som er merket Ignorer personvernnivåene og potensielt forbedre ytelsen). Men hva om du vil la brannmuren være aktivert?

Hvis du vil løse feilen uten å deaktivere brannmuren, kan du kombinere firma og søk i én enkelt spørring, slik som dette:

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 skjer nå inne i én enkelt partisjon. Forutsatt at personvernnivåene for de to datakildene er kompatible, bør brannmuren nå være fornøyd, og du får ikke lenger en feil.

Det er en wrap

Selv om det er mye mer som kan sies om dette emnet, er denne innledende artikkelen allerede lang nok. Forhåpentligvis har det gitt deg en bedre forståelse av brannmuren, og vil hjelpe deg med å forstå og løse brannmurfeil når du støter på dem i fremtiden.