Za kulisami zapory prywatności danych

Uwaga

Poziomy prywatności są obecnie niedostępne w przepływach danych platformy Power Platform, ale zespół produktu pracuje nad włączeniem tej funkcji.

Jeśli używasz dodatku Power Query przez dowolną długość czasu, prawdopodobnie go doświadczyłeś. W przypadku nagłego wystąpienia błędu występuje błąd, że nie ma możliwości wyszukiwania w trybie online, dostosowywania zapytań ani bashingu klawiaturowego. Błąd podobny do:

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.

A może:

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

Te Formula.Firewall błędy są wynikiem zapory prywatności danych dodatku Power Query (znanej również jako Zapora), która czasami może wydawać się, że istnieje wyłącznie w celu sfrustrowania analityków danych na całym świecie. Uważaj jednak, że zapora służy ważnemu celowi. W tym artykule zagłębimy się w maskę, aby lepiej zrozumieć, jak to działa. Uzbrojony w lepsze zrozumienie, będziesz mieć nadzieję, że będziesz w stanie lepiej zdiagnozować i naprawić błędy zapory w przyszłości.

Co to jest?

Celem zapory prywatności danych jest proste: istnieje, aby zapobiec przypadkowemu wyciekowi danych między źródłami przez dodatek Power Query.

Dlaczego jest to konieczne? Myślę, że z pewnością można utworzyć kilka M, które przekażą wartość SQL do źródła danych OData. Jednak byłoby to zamierzone wycieki danych. Autor mashupu (lub przynajmniej powinien) wiedzieć, że to robi. Dlaczego zatem potrzeba ochrony przed niezamierzonym wyciekiem danych?

Odpowiedź? Składane.

Składane?

Składanie to termin, który odnosi się do konwertowania wyrażeń w języku M (takich jak filtry, zmiany nazw, sprzężenia itd.) na operacje względem nieprzetworzonego źródła danych (takiego jak SQL, OData itd.). Ogromną częścią mocy dodatku Power Query jest fakt, że PQ może konwertować operacje wykonywane przez użytkownika za pośrednictwem interfejsu użytkownika na złożone języki JĘZYKA SQL lub innych źródeł danych zaplecza, bez konieczności znajomości tych języków przez użytkownika. Użytkownicy uzyskują korzyści z wydajności operacji natywnych źródeł danych, z łatwością korzystania z interfejsu użytkownika, w którym można przekształcić wszystkie źródła danych przy użyciu wspólnego zestawu poleceń.

W ramach składania PQ czasami może określić, że najbardziej efektywnym sposobem wykonania danego mashupu jest przejęcie danych z jednego źródła i przekazanie ich do innego. Jeśli na przykład dołączasz mały plik CSV do ogromnej tabeli SQL, prawdopodobnie nie chcesz, aby PQ odczytywał plik CSV, odczytywał całą tabelę SQL, a następnie łączył je ze sobą na komputerze lokalnym. Prawdopodobnie chcesz, aby PQ wstawić dane CSV do instrukcji SQL i poprosić bazę danych SQL o wykonanie sprzężenia.

W ten sposób może wystąpić niezamierzony wyciek danych.

Wyobraź sobie, że dołączasz do danych SQL, które zawierały numery ubezpieczenia społecznego pracownika z wynikami zewnętrznego źródła danych OData, i nagle okazało się, że numery ubezpieczenia społecznego z bazy danych SQL były wysyłane do usługi OData. Zła wiadomość, prawda?

Jest to rodzaj scenariusza, w jakim zapora ma zapobiegać.

Jak to działa?

Zapora istnieje, aby zapobiec przypadkowemu wysyłaniu danych z jednego źródła do innego źródła. Wystarczająco proste.

W jaki sposób realizuje tę misję?

Robi to przez podzielenie zapytań języka M na elementy nazywane partycjami, a następnie wymuszanie następującej reguły:

  • Partycja może uzyskiwać dostęp do zgodnych źródeł danych lub odwoływać się do innych partycji, ale nie do obu.

Prosty... ale mylące. Co to jest partycja? Co sprawia, że dwa źródła danych są "zgodne"? Dlaczego warto zadbać o zaporę, jeśli partycja chce uzyskać dostęp do źródła danych i odwołać się do partycji?

Przyjrzyjmy się tej regule pojedynczo i przyjrzyjmy się powyższej regule.

Co to jest partycja?

Na najbardziej podstawowym poziomie partycja jest tylko kolekcją co najmniej jednego kroku zapytania. Najbardziej szczegółowa partycja możliwa (przynajmniej w bieżącej implementacji) to jeden krok. Największe partycje mogą czasami obejmować wiele zapytań. (Więcej informacji na ten temat później).

Jeśli nie znasz kroków, możesz je wyświetlić po prawej stronie okna Edytor Power Query po wybraniu zapytania w okienku Zastosowane kroki. Kroki śledzą wszystko, co zrobiliśmy, aby przekształcić dane w jego ostateczny kształt.

Partycje odwołujące się do innych partycji

Gdy zapytanie jest oceniane przy użyciu zapory, zapora dzieli zapytanie i wszystkie jego zależności na partycje (czyli grupy kroków). Za każdym razem, gdy jedna partycja odwołuje się do innej partycji, zapora zastępuje odwołanie wywołaniem specjalnej funkcji o nazwie Value.Firewall. Innymi słowy zapora nie zezwala na bezpośrednie uzyskiwanie dostępu do partycji. Wszystkie odwołania są modyfikowane w celu przejścia przez zaporę. Myśl o zaporze jako strażniku. Partycja, która odwołuje się do innej partycji, musi uzyskać uprawnienie Zapory do tego celu, a zapora kontroluje, czy przywołyne dane będą dozwolone w partycji.

To wszystko może wydawać się dość abstrakcyjne, więc przyjrzyjmy się przykładowi.

Załóżmy, że masz zapytanie o nazwie Employees, które ściąga niektóre dane z bazy danych SQL. Załóżmy, że masz również inne zapytanie (EmployeesReference), które po prostu odwołuje się do pracowników.

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

shared EmployeesReference = let
    Source = Employees
in
    Source;

Te zapytania zostaną podzielone na dwie partycje: jedną dla zapytania Employees i jedną dla zapytania EmployeesReference (która będzie odwoływać się do partycji Employees). Po ocenie przy użyciu zapory te zapytania zostaną przepisane w następujący sposób:

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

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

Zwróć uwagę, że proste odwołanie do zapytania Employees zostało zastąpione wywołaniem metody Value.Firewall, która jest podana pełna nazwa zapytania Employees.

Gdy funkcja EmployeesReference jest oceniana, wywołanie Value.Firewall("Section1/Employees") metody jest przechwytywane przez zaporę, która ma teraz możliwość kontrolowania, czy (i jak) żądane dane przepływają do partycji EmployeesReference. Może to zrobić dowolną liczbę rzeczy: odmówić żądania, buforować żądane dane (co uniemożliwia dalsze składanie do oryginalnego źródła danych) itd.

W ten sposób zapora zachowuje kontrolę nad danymi przepływającym między partycjami.

Partycje, które uzyskują bezpośredni dostęp do źródeł danych

Załóżmy, że zdefiniujesz zapytanie Query1 za pomocą jednego kroku (należy pamiętać, że to jednoetapowe zapytanie odpowiada jednej partycji Zapory) i że ten pojedynczy krok uzyskuje dostęp do dwóch źródeł danych: tabeli bazy danych SQL i pliku CSV. W jaki sposób zapora zajmuje się tym, ponieważ nie ma odwołania do partycji, a tym samym nie ma wywołania go do Value.Firewall przechwycenia? Przyjrzyjmy się wcześniej podanej regule:

  • Partycja może uzyskiwać dostęp do zgodnych źródeł danych lub odwoływać się do innych partycji, ale nie do obu.

Aby można było uruchomić zapytanie z jedną partycją, ale dwa źródła danych, muszą być "zgodne". Innymi słowy, dane muszą być współużytkowane dwukierunkowo między nimi. Oznacza to, że poziomy prywatności obu źródeł muszą być publiczne lub oba te typy są organizacyjne, ponieważ są to jedyne dwie kombinacje, które umożliwiają udostępnianie w obu kierunkach. Jeśli oba źródła są oznaczone jako Prywatne lub jeden jest oznaczony jako Publiczny, a jeden jest oznaczony jako Organizacyjny lub jest oznaczony przy użyciu innej kombinacji poziomów prywatności, udostępnianie dwukierunkowe nie jest dozwolone i dlatego nie jest bezpieczne, aby oba te źródła były oceniane w tej samej partycji. Oznaczałoby to, że może wystąpić niebezpieczny wyciek danych (z powodu składania), a zapora nie byłaby w stanie temu zapobiec.

Co się stanie, jeśli spróbujesz uzyskać dostęp do niezgodnych źródeł danych w tej samej partycji?

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

Mam nadzieję, że teraz lepiej zrozumiesz jeden z komunikatów o błędach wymienionych na początku tego artykułu.

Należy pamiętać, że to wymaganie zgodności ma zastosowanie tylko w ramach danej partycji. Jeśli partycja odwołuje się do innych partycji, źródła danych z przywołynych partycji nie muszą być ze sobą zgodne. Dzieje się tak, ponieważ zapora może buforować dane, co uniemożliwi dalsze składanie danych w oryginalnym źródle danych. Dane zostaną załadowane do pamięci i będą traktowane tak, jakby pochodziły znikąd.

Dlaczego nie obie?

Załóżmy, że zdefiniujesz zapytanie z jednym krokiem (który ponownie odpowiada jednej partycji), która uzyskuje dostęp do dwóch innych zapytań (czyli dwóch innych partycji). Co zrobić, jeśli chcesz, w tym samym kroku, aby bezpośrednio uzyskać dostęp do bazy danych SQL? Dlaczego partycja nie może odwoływać się do innych partycji i bezpośrednio uzyskać dostępu do zgodnych źródeł danych?

Jak pokazano wcześniej, gdy jedna partycja odwołuje się do innej partycji, zapora działa jako strażnik dla wszystkich danych przepływających do partycji. Aby to zrobić, musi mieć możliwość kontrolowania dozwolonych danych. Jeśli w partycji są dostępne źródła danych, a dane przepływające z innych partycji tracą możliwość bycia strażnikiem, ponieważ przepływające dane mogą zostać ujawnione do jednego z wewnętrznie używanych źródeł danych, nie wiedząc o tym. W związku z tym zapora uniemożliwia partycji uzyskującej dostęp do innych partycji w celu bezpośredniego uzyskiwania dostępu do dowolnych źródeł danych.

Co się dzieje, jeśli partycja próbuje odwołać się do innych partycji, a także bezpośrednio uzyskać dostęp do źródeł danych?

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.

Teraz miejmy nadzieję, że lepiej zrozumiesz inny komunikat o błędzie wymieniony na początku tego artykułu.

Szczegółowe partycje

Jak można chyba odgadnąć z powyższych informacji, sposób partycjonowania zapytań kończy się niezwykle ważne. Jeśli masz kilka kroków odwołujących się do innych zapytań i innych kroków, które uzyskują dostęp do źródeł danych, teraz masz nadzieję rozpoznać, że rysowanie granic partycji w niektórych miejscach spowoduje błędy zapory, podczas gdy rysowanie ich w innych miejscach pozwoli na uruchomienie zapytania w porządku.

Jak dokładnie zapytania są partycjonowane?

Ta sekcja jest prawdopodobnie najważniejsza, aby zrozumieć, dlaczego występują błędy zapory i zrozumieć, jak je rozwiązać (tam, gdzie to możliwe).

Poniżej przedstawiono ogólne podsumowanie logiki partycjonowania.

  • Partycjonowanie początkowe
    • Tworzy partycję dla każdego kroku w każdym zapytaniu
  • Faza statyczna
    • Ta faza nie zależy od wyników oceny. Zamiast tego opiera się na strukturę zapytań.
      • Przycinanie parametrów
        • Przycina partycje parametr-esque, czyli dowolne, które:
          • Nie odwołuje się do żadnych innych partycji
          • Nie zawiera żadnych wywołań funkcji
          • Nie jest cykliczny (czyli nie odnosi się do samego siebie)
        • Należy pamiętać, że "usuwanie" partycji skutecznie uwzględnia ją w innych partycjach, do których się odwołuje.
        • Przycinanie partycji parametrów umożliwia używanie odwołań parametrów w wywołaniach funkcji źródła danych (na przykład Web.Contents(myUrl)) do działania, zamiast zgłaszać błędy "partycja nie może odwoływać się do źródeł danych i innych kroków".
      • Grupowanie (statyczne)
        • Partycje są scalane w kolejności zależności od dołu. W wynikowych scalonych partycjach następujące elementy będą oddzielone:
          • Partycje w różnych zapytaniach
          • Partycje, które nie odwołują się do innych partycji (i w związku z tym mogą uzyskiwać dostęp do źródła danych)
          • Partycje odwołujące się do innych partycji (i w związku z tym nie mogą uzyskiwać dostępu do źródła danych)
  • Faza dynamiczna
    • Ta faza zależy od wyników oceny, w tym informacji o źródłach danych dostępnych przez różne partycje.
    • Przycinanie
      • Przycina partycje spełniające wszystkie następujące wymagania:
        • Nie uzyskuje dostępu do żadnych źródeł danych
        • Nie odwołuje się do żadnych partycji, które uzyskują dostęp do źródeł danych
        • Nie jest cykliczne
    • Grupowanie (dynamiczne)
      • Teraz, gdy niepotrzebne partycje zostały przycięte, spróbuj utworzyć partycje źródłowe, które są tak duże, jak to możliwe. Odbywa się to przez scalenie partycji przy użyciu tych samych reguł opisanych w powyższej fazie grupowania statycznego.

Co to wszystko oznacza?

Przyjrzyjmy się przykładowi, aby zilustrować sposób działania złożonej logiki przedstawionej powyżej.

Oto przykładowy scenariusz. Jest to dość proste scalanie pliku tekstowego (kontaktów) z bazą danych SQL (Employees), gdzie serwer SQL jest parametrem (DbServer).

Trzy zapytania

Oto kod języka M dla trzech zapytań używanych w tym przykładzie.

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

Oto widok wyższego poziomu przedstawiający zależności.

Okno dialogowe Zależności zapytania.

Podzielmy się na partycje

Powiększmy nieco i uwzględnijmy kroki na obrazie, a następnie zacznijmy przechodzić przez logikę partycjonowania. Oto diagram trzech zapytań przedstawiający początkowe partycje zapory w kolorze zielonym. Zwróć uwagę, że każdy krok rozpoczyna się we własnej partycji.

Początkowe partycje zapory.

Następnie przycinamy partycje parametrów. W związku z tym serwer DbServer jest niejawnie dołączany do partycji źródłowej.

Przycięte partycje zapory.

Teraz przeprowadzamy grupowanie statyczne. Zachowuje to separację między partycjami w oddzielnych zapytaniach (należy pamiętać na przykład, że dwa ostatnie kroki pracowników nie są pogrupowane z krokami kontaktów) i między partycjami odwołującymi się do innych partycji (takich jak ostatnie dwa kroki pracowników) i tych, które nie (takie jak pierwsze trzy kroki pracowników).

Opublikuj partycje zapory grupowania statycznego.

Teraz wprowadzamy fazę dynamiczną. W tej fazie oceniane są powyższe partycje statyczne. Partycje, które nie uzyskują dostępu do żadnych źródeł danych, są przycinane. Następnie partycje są grupowane w celu utworzenia partycji źródłowych, które są tak duże, jak to możliwe. Jednak w tym przykładowym scenariuszu wszystkie pozostałe partycje uzyskują dostęp do źródeł danych i nie ma żadnych dalszych grup, które można wykonać. Partycje w naszym przykładzie nie zmienią się w tej fazie.

Udajmy

Ze względu na ilustrację przyjrzyjmy się jednak temu, co się stanie, jeśli zapytanie Kontakty zamiast pochodzić z pliku tekstowego, było zakodowane w języku M (być może za pośrednictwem okna dialogowego Wprowadzanie danych ).

W takim przypadku zapytanie Kontakty nie będzie uzyskiwać dostępu do żadnych źródeł danych. W ten sposób zostanie ona przycięta w pierwszej części fazy dynamicznej.

Partycja zapory po przycinaniu fazy dynamicznej.

Po usunięciu partycji Kontakty ostatnie dwa kroki pracowników nie będą już odwoływać się do żadnych partycji, z wyjątkiem tych, które zawierają pierwsze trzy kroki pracowników. W związku z tym dwie partycje zostaną zgrupowane.

Wynikowa partycja będzie wyglądać następująco.

Końcowe partycje zapory.

Przykład: przekazywanie danych z jednego źródła danych do innego

Ok, wystarczająco abstrakcyjne wyjaśnienie. Przyjrzyjmy się typowemu scenariuszowi, w którym prawdopodobnie wystąpi błąd zapory i kroki umożliwiające jego rozwiązanie.

Wyobraź sobie, że chcesz wyszukać nazwę firmy z usługi Northwind OData, a następnie użyć nazwy firmy do wyszukiwania Bing.

Najpierw należy utworzyć zapytanie firmowe w celu pobrania nazwy firmy.

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

Następnie utworzysz zapytanie wyszukiwania , które odwołuje się do firmy i przekazuje je do usługi Bing.

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

W tym momencie napotkasz problemy. Ocena wyszukiwania powoduje błąd zapory.

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.

Wynika to z faktu, że krok Źródło wyszukiwania odwołuje się do źródła danych (bing.com), a także odwołuje się do innej kwerendy/partycji (firma). Narusza on regułę wymienioną powyżej ("partycja może uzyskiwać dostęp do zgodnych źródeł danych lub odwoływać się do innych partycji, ale nie obu").

Postępowanie Jedną z opcji jest całkowite wyłączenie zapory (za pośrednictwem opcji Prywatność z etykietą Ignoruj poziomy prywatności i potencjalnie poprawić wydajność). Ale co zrobić, jeśli chcesz pozostawić włączoną zaporę?

Aby rozwiązać ten problem bez wyłączania zapory, możesz połączyć aplikację Company i Search w jedno zapytanie, w następujący sposób:

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

Wszystko dzieje się teraz wewnątrz jednej partycji. Zakładając, że poziomy prywatności dla dwóch źródeł danych są zgodne, zapora powinna być teraz szczęśliwa i nie otrzymasz już błędu.

To zawijanie

Chociaż jest o wiele więcej, które można powiedzieć na ten temat, ten artykuł wprowadzający jest już wystarczająco długi. Mam nadzieję, że otrzymasz lepsze zrozumienie zapory i pomoże Ci zrozumieć i naprawić błędy zapory, gdy wystąpią one w przyszłości.