Bakom kulisserna i brandväggen för datasekretess
Kommentar
Sekretessnivåer är för närvarande inte tillgängliga i Power Platform-dataflöden, men produktteamet arbetar för att aktivera den här funktionen.
Om du har använt Power Query under en längre tid har du förmodligen upplevt det. Där är du, frågar bort, när du plötsligt får ett fel som ingen mängd onlinesökning, frågejustering eller tangentbord bashing kan åtgärda. Ett fel 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 kanske:
Formula.Firewall: Query 'Query1' (step 'Source') is accessing data sources that have privacy levels which cannot be used together. Please rebuild this data combination.
Dessa Formula.Firewall
fel är resultatet av Power Querys brandvägg för datasekretess (även kallad brandväggen), som ibland kan verka som om den bara finns för att hindra dataanalytiker världen över. Tro det eller ej, men brandväggen tjänar ett viktigt syfte. I den här artikeln ska vi gå under huven för att bättre förstå hur det fungerar. Beväpnad med större förståelse kan du förhoppningsvis bättre diagnostisera och åtgärda brandväggsfel i framtiden.
Vad är det?
Syftet med brandväggen för datasekretess är enkelt: det finns för att förhindra att Power Query oavsiktligt läcker data mellan källor.
Varför krävs detta? Jag menar, du kan säkert skriva några M som skulle skicka ett SQL-värde till en OData-feed. Men det skulle vara avsiktligt dataläckage. Mashup-författaren skulle (eller åtminstone borde) veta att de gjorde detta. Varför behövs då skydd mot oavsiktligt dataläckage?
Svaret? Fällbara.
Fällbara?
Vikning är en term som refererar till konvertering av uttryck i M (till exempel filter, namnbyten, kopplingar och så vidare) till åtgärder mot en rådatakälla (till exempel SQL, OData och så vidare). En stor del av Power Querys kraft kommer från det faktum att PQ kan konvertera de åtgärder som en användare utför via sitt användargränssnitt till komplexa SQL- eller andra serverdelsdatakällspråk, utan att användaren behöver känna till dessa språk. Användarna får prestandafördelarna med interna datakällsåtgärder, med enkel användning av ett användargränssnitt där alla datakällor kan transformeras med hjälp av en gemensam uppsättning kommandon.
Som en del av vikningen kan PQ ibland avgöra att det mest effektiva sättet att köra en viss kombination är att ta data från en källa och skicka dem till en annan. Om du till exempel ansluter en liten CSV-fil till en enorm SQL-tabell vill du förmodligen inte att PQ ska läsa CSV-filen, läsa hela SQL-tabellen och sedan koppla ihop dem på den lokala datorn. Du vill förmodligen att PQ ska infoga CSV-data i en SQL-instruktion och be SQL-databasen att utföra kopplingen.
Så här kan oavsiktligt dataläckage inträffa.
Tänk dig om du anslöt SQL-data som inkluderade socialförsäkringsnummer för anställda med resultatet av ett externt OData-flöde, och du plötsligt upptäckte att personnummer från SQL skickades till OData-tjänsten. Dåliga nyheter, eller hur?
Det här är den typ av scenario som brandväggen är avsedd att förhindra.
Hur fungerar det?
Brandväggen finns för att förhindra att data från en källa oavsiktligt skickas till en annan källa. Enkelt nog.
Så hur utför det detta uppdrag?
Det gör du genom att dela upp dina M-frågor i något som kallas partitioner och sedan framtvinga följande regel:
- En partition kan antingen komma åt kompatibla datakällor eller referera till andra partitioner, men inte båda.
Enkel... men ändå förvirrande. Vad är en partition? Vad gör två datakällor "kompatibla"? Och varför ska brandväggen bry sig om en partition vill komma åt en datakälla och referera till en partition?
Låt oss dela upp detta och titta på ovanstående regel en bit i taget.
Vad är en partition?
På den mest grundläggande nivån är en partition bara en samling med ett eller flera frågesteg. Den mest detaljerade partitionen som är möjlig (åtminstone i den aktuella implementeringen) är ett enda steg. De största partitionerna kan ibland omfatta flera frågor. (Mer information om detta senare.)
Om du inte är bekant med steg kan du visa dem till höger om Power Query-redigeraren-fönstret när du har valt en fråga i fönstret Tillämpade steg. Steg håller reda på allt du har gjort för att omvandla dina data till sin slutliga form.
Partitioner som refererar till andra partitioner
När en fråga utvärderas med brandväggen på delar brandväggen upp frågan och alla dess beroenden i partitioner (det vill: grupper med steg). När en partition refererar till något i en annan partition ersätter brandväggen referensen med ett anrop till en särskild funktion som heter Value.Firewall
. Med andra ord tillåter brandväggen inte partitioner att komma åt varandra direkt. Alla referenser ändras för att gå igenom brandväggen. Tänk på brandväggen som en gatekeeper. En partition som refererar till en annan partition måste få brandväggens behörighet att göra det, och brandväggen styr om de refererade data ska tillåtas i partitionen eller inte.
Allt detta kan verka ganska abstrakt, så låt oss titta på ett exempel.
Anta att du har en fråga med namnet Employees, som hämtar vissa data från en SQL-databas. Anta att du också har en annan fråga (EmployeesReference), som bara refererar till Anställda.
shared Employees = let
Source = Sql.Database(…),
EmployeesTable = …
in
EmployeesTable;
shared EmployeesReference = let
Source = Employees
in
Source;
De här frågorna delas upp i två partitioner: en för frågan Anställda och en för frågan EmployeesReference (som refererar till partitionen Anställda). När de utvärderas med brandväggen på skrivs dessa frågor om så här:
shared Employees = let
Source = Sql.Database(…),
EmployeesTable = …
in
EmployeesTable;
shared EmployeesReference = let
Source = Value.Firewall("Section1/Employees")
in
Source;
Observera att den enkla referensen till frågan Anställda har ersatts av ett anrop till Value.Firewall
, som har angett det fullständiga namnet på frågan Anställda.
När EmployeesReference utvärderas fångas anropet till Value.Firewall("Section1/Employees")
upp av brandväggen, som nu har möjlighet att kontrollera om (och hur) de begärda data flödar till partitionen EmployeesReference. Den kan göra valfritt antal saker: neka begäran, buffra begärda data (vilket förhindrar att ytterligare vikning till den ursprungliga datakällan sker) och så vidare.
Det är så brandväggen behåller kontrollen över data som flödar mellan partitioner.
Partitioner som har direkt åtkomst till datakällor
Anta att du definierar en fråga Query1 med ett steg (observera att den här enstegsfrågan motsvarar en brandväggspartition) och att det här enskilda steget kommer åt två datakällor: en SQL-databastabell och en CSV-fil. Hur hanterar brandväggen detta, eftersom det inte finns någon partitionsreferens och därmed inget anrop till Value.Firewall
för att den ska avlyssnas? Nu ska vi gå igenom regeln som angavs tidigare:
- En partition kan antingen komma åt kompatibla datakällor eller referera till andra partitioner, men inte båda.
För att din fråga med en partition men två datakällor ska kunna köras måste dess två datakällor vara "kompatibla". Med andra ord måste det vara okej att data delas dubbelriktat mellan dem. Det innebär att sekretessnivåerna för båda källorna måste vara offentliga, eller båda är organisatoriska, eftersom dessa är de enda två kombinationerna som tillåter delning i båda riktningarna. Om båda källorna har markerats som Privata, eller om en är markerad som Offentlig och en är markerad som Organisation, eller om de markeras med någon annan kombination av sekretessnivåer, tillåts inte dubbelriktad delning och det är därför inte säkert att båda utvärderas i samma partition. Detta skulle innebära att osäkert dataläckage kan uppstå (på grund av vikning) och brandväggen skulle inte ha något sätt att förhindra det.
Vad händer om du försöker komma åt inkompatibla datakällor i samma 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.
Förhoppningsvis kan du nu bättre förstå ett av de felmeddelanden som visas i början av den här artikeln.
Observera att det här kompatibilitetskravet endast gäller inom en viss partition. Om en partition refererar till andra partitioner behöver datakällorna från de refererade partitionerna inte vara kompatibla med varandra. Det beror på att brandväggen kan buffrar data, vilket förhindrar ytterligare vikning mot den ursprungliga datakällan. Data läses in i minnet och behandlas som om de kom från ingenstans.
Varför inte göra båda?
Anta att du definierar en fråga med ett steg (som återigen motsvarar en partition) som har åtkomst till två andra frågor (det vill säga två andra partitioner). Vad händer om du i samma steg vill ha direkt åtkomst till en SQL-databas? Varför kan inte en partition referera till andra partitioner och komma åt kompatibla datakällor direkt?
Som du såg tidigare fungerar brandväggen som gatekeeper för alla data som flödar in i partitionen när en partition refererar till en annan partition. För att kunna göra det måste den kunna styra vilka data som tillåts i. Om det finns datakällor som används i partitionen och data flödar in från andra partitioner förlorar den möjligheten att vara gatekeeper eftersom data som flödar in kan läckas till en av de internt använda datakällorna utan att den känner till den. Brandväggen förhindrar därför att en partition som har åtkomst till andra partitioner får direkt åtkomst till datakällor.
Så vad händer om en partition försöker referera till andra partitioner och även direkt komma åt datakällor?
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 förhoppningsvis bättre förstå det andra felmeddelandet som visas i början av den här artikeln.
Partitioner på djupet
Som du förmodligen kan gissa från ovanstående information blir det otroligt viktigt hur frågor partitioneras. Om du har några steg som refererar till andra frågor och andra steg som har åtkomst till datakällor kan du nu förhoppningsvis känna igen att om du ritar partitionsgränserna på vissa platser kan brandväggsfel uppstå, samtidigt som du ritar dem på andra platser så att frågan kan köras bra.
Så hur exakt partitioneras frågor?
Det här avsnittet är förmodligen det viktigaste för att förstå varför du ser brandväggsfel och förstå hur du löser dem (där det är möjligt).
Här är en översikt över partitioneringslogik.
- Inledande partitionering
- Skapar en partition för varje steg i varje fråga
- Statisk fas
- Den här fasen är inte beroende av utvärderingsresultat. I stället förlitar den sig på hur frågorna är strukturerade.
- Parameter trimning
- Trimmar parameterliknande partitioner, det vill:
- Refererar inte till några andra partitioner
- Innehåller inga funktionsanrop
- Är inte cyklisk (det vill: den refererar inte till sig själv)
- Observera att "ta bort" en partition innehåller den i alla andra partitioner som refererar till den.
- Genom att trimma parameterpartitioner kan parameterreferenser som används i datakällans funktionsanrop (till exempel
Web.Contents(myUrl)
) fungera, i stället för att utlösa fel med "partition kan inte referera till datakällor och andra steg".
- Trimmar parameterliknande partitioner, det vill:
- Gruppering (statisk)
- Partitioner sammanfogas i beroendeordning nedifrån och upp. I de resulterande sammanfogade partitionerna är följande separata:
- Partitioner i olika frågor
- Partitioner som inte refererar till andra partitioner (och som därmed får åtkomst till en datakälla)
- Partitioner som refererar till andra partitioner (och är därför förbjudna att komma åt en datakälla)
- Partitioner sammanfogas i beroendeordning nedifrån och upp. I de resulterande sammanfogade partitionerna är följande separata:
- Parameter trimning
- Den här fasen är inte beroende av utvärderingsresultat. I stället förlitar den sig på hur frågorna är strukturerade.
- Dynamisk fas
- Den här fasen beror på utvärderingsresultat, inklusive information om datakällor som används av olika partitioner.
- Trimning
- Trimmar partitioner som uppfyller följande krav:
- Har inte åtkomst till några datakällor
- Refererar inte till partitioner som har åtkomst till datakällor
- Är inte cyklisk
- Trimmar partitioner som uppfyller följande krav:
- Gruppering (dynamisk)
- Nu när onödiga partitioner har trimmats kan du försöka skapa källpartitioner som är så stora som möjligt. Detta görs genom att sammanfoga partitionerna med samma regler som beskrivs i den statiska grupperingsfasen ovan.
Vad betyder allt det här?
Nu ska vi gå igenom ett exempel för att illustrera hur den komplexa logik som beskrivs ovan fungerar.
Här är ett exempelscenario. Det är en ganska enkel sammanslagning av en textfil (Kontakter) med en SQL-databas (Employees), där SQL-servern är en parameter (DbServer).
De tre frågorna
Här är M-koden för de tre frågor som används i det här exemplet.
shared DbServer = "MySqlServer" 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";
Här är en vy på högre nivå som visar beroendena.
Nu ska vi partitioneras
Nu ska vi zooma in lite och ta med steg i bilden och börja gå igenom partitioneringslogik. Här är ett diagram över de tre frågorna som visar de första brandväggspartitionerna i grönt. Observera att varje steg startar i en egen partition.
Därefter trimmar vi parameterpartitioner. Därför inkluderas DbServer implicit i källpartitionen.
Nu utför vi den statiska grupperingen. Detta upprätthåller separation mellan partitioner i separata frågor (observera till exempel att de två sista stegen i Anställda inte grupperas med stegen i Kontakter) och mellan partitioner som refererar till andra partitioner (till exempel de två sista stegen i Anställda) och de som inte gör det (till exempel de tre första stegen i Anställda).
Nu går vi in i den dynamiska fasen. I den här fasen utvärderas ovanstående statiska partitioner. Partitioner som inte har åtkomst till några datakällor trimmas. Partitioner grupperas sedan för att skapa källpartitioner som är så stora som möjligt. I det här exempelscenariot får dock alla återstående partitioner åtkomst till datakällor och det finns ingen ytterligare gruppering som kan göras. Partitionerna i vårt exempel ändras därför inte under den här fasen.
Låt oss låtsas
För illustrationens skull ska vi dock titta på vad som skulle hända om frågan Kontakter, i stället för att komma från en textfil, hårdkodades i M (kanske via dialogrutan Ange data ).
I det här fallet skulle frågan Kontakter inte komma åt några datakällor. Därför skulle den trimmas under den första delen av den dynamiska fasen.
När partitionen Kontakter har tagits bort refererar de två sista stegen i Anställda inte längre till några partitioner förutom den som innehåller de tre första stegen i Anställda. Därför grupperas de två partitionerna.
Den resulterande partitionen skulle se ut så här.
Exempel: Skicka data från en datakälla till en annan
Okej, nog med abstrakt förklaring. Låt oss titta på ett vanligt scenario där du sannolikt kommer att stöta på ett brandväggsfel och stegen för att lösa det.
Anta att du vill söka efter ett företagsnamn från Northwind OData-tjänsten och sedan använda företagsnamnet för att utföra en Bing-sökning.
Först skapar du en företagsfråga för att hämta företagsnamnet.
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
Därefter skapar du en sökfråga som refererar till Företaget och skickar den till Bing.
let
Source = Text.FromBinary(Web.Contents("https://www.bing.com/search?q=" & Company))
in
Source
Nu stöter du på problem. Utvärdering av sökning genererar ett brandväggsfel.
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 beror på att källsteget i Sök refererar till en datakälla (bing.com) och refererar även till en annan fråga/partition (företag). Det bryter mot regeln som nämns ovan ("en partition kan antingen komma åt kompatibla datakällor eller referera till andra partitioner, men inte båda").
Vad bör jag göra? Ett alternativ är att inaktivera brandväggen helt och hållet (via alternativet Sekretess med etiketten Ignorera sekretessnivåer och eventuellt förbättra prestanda). Men vad händer om du vill lämna brandväggen aktiverad?
Om du vill lösa felet utan att inaktivera brandväggen kan du kombinera Företag och Sök i en enda fråga, så här:
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
Allt händer nu i en enda partition. Om vi antar att sekretessnivåerna för de två datakällorna är kompatibla bör brandväggen nu vara nöjd och du får inte längre något fel.
Det är en wrap
Även om det finns mycket mer som kan sägas om det här ämnet, är den här introduktionsartikeln redan tillräckligt lång. Förhoppningsvis har du fått en bättre förståelse för brandväggen och hjälper dig att förstå och åtgärda brandväggsfel när du stöter på dem i framtiden.