Wróć do strony głównej
Angular

Angular & Electron cz. 2

W poprzednim artykule udało nam się przejść przez najważniejsze aspekty związane z Electronem oraz jego bezpieczeństwem. Uzbrojeni w nową wiedzę oraz najlepsze praktyki możemy się zabrać za integrację przykładowej aplikacji Angularowej.

Podczas omawiania komunikacji między rendererem a mainem przywołaliśmy kilka możliwych sposobów na jej realizację, niektóre mniej a niektóre bardziej zalecane.

Ze względów dydaktycznych, naszą aplikację zintegrujemy z Electronem realizując wspomnianą komunikację na każdy z możliwych sposobów. Całość dostępna jest w publicznym repozytorium.

  1. Aplikacja
  2. Remote (ngx-electron)
  3. IPC (ngx-electron)
  4. Preload
  5. PostMessage API
  6. Context Bridge
  7. Lokalny Serwer
  8. Podsumowanie
  9. Przydatne linki

Aplikacja

W ramach integracji stworzymy przykładową aplikację wykorzystującą w jakiś sposób natywne funkcjonalności jakie udostępnia nam Electron.

W celach demonstracyjnych zaimplementujemy możliwość wyświetlenia natywnego dialogu oraz natywnych notyfikacji informujących o rzekomych obliczeniach wykonywanych po stronie serwera.

Jeśli chodzi o Electronową konfigurację skorzystamy z wspomnianego wcześniej szablonu electron-secure-defaults.

Nasz aplikacja prezentuje się następująco:

Poglądowa aplikacja Electronowa wykorzystująca natywne funkcje danej platformy.

Jako że mamy już gotowy szkielet naszej aplikacji, nadszedł czas na integrację z jej serwerową częścią.

Remote (ngx-electron)

Na pierwszy ogień idzie nie zalecany już aktualnie moduł @electron/remote, dający nam bezpośredni dostęp do maina z poziomu renderera. Jak pamiętacie z wcześniejszych rozdziałów, tego typu furtka może mieć poważne konsekwencje jeśli chodzi o bezpieczeństwo.

Pokażemy Wam jak coś takiego skonfigurować, oczywiście nie zalecamy stosowania tego podejścia w praktyce.

Wykorzystamy w tym celu paczki ngx-electron oraz @electron/remote. Wykonujemy polecenie:

Następnie moduł remote musimy zainicjalizować, wywołując odpowiednią metodę w ramach inicjalizacji naszej aplikacji, w pliku main.ts.

Aby była możliwość wykorzystania tego modułu, musimy również znacząco obniżyć poziom zabezpieczeń naszego okna ustawiając wybrane flagi na:

  • nodeIntegration: true
  • contextIsolation: false
  • enableRemoteModule: true
  • sandbox: false

Ponadto musimy wyłączyć wywołanie app.enableSandbox().

Po wstępnej konfiguracji ngx-electron, co sprowadza się do zaimportowania modułu oraz wstrzyknięcia serwisu, możemy skorzystać z omawianego modułu remote i wywołać natywne funkcjonalności z poziomu naszej aplikacji.

Kod serwisu odpowiedzialnego za wywoływanie wspomnianych funkcji wygląda następująco:

Serwis wykorzystujący moduł remote w celu wyświetlania dialogu oraz notyfikacji.

IPC (ngx-electron)

Przechodzimy do wykorzystania IPC w celu komunikacji między aplikacją a naszym backendem. Skorzystamy tutaj ponownie z paczki ngx-electron oraz z kontrolera NestJs.

Instalujemy najpierw naszą paczkę:

Ponownie abyśmy bezpośrednio mogli wykorzystać ipcMain oraz ipcRenderer musimy odpowiednio ustawić flagi związane z bezpieczeństwem:

  • nodeIntegration: true
  • contextIsolation: false
  • sandbox: false

Musimy również wyłączyć wywołanie app.enableSandbox().

Przechodząc do implementacji naszego backendu w NestJs, definiujemy nasz kontroler:

Kontroler obsługujący konkretne wiadomości.

Obsługa okien dialogowych oraz notyfikacji znajduje się w serwisie AppService. Implementacja wygląda analogicznie jak w przypadku @electron/remote z tą różnicą, że operujemy bezpośrednio na modułach Electrona.

Od strony aplikacji implementacja sprowadza się ponownie do wykorzystania serwisu udostępnionego nam przez paczkę ngx-electron, tym razem jednak odwołujemy się do ipcRenderer zamiast do remote.

Serwis odpowiadający za wysyłanie wiadomości do backendu.

Rozwiązanie to wydaje się bezpieczniejsze, w dalszym jednak ciągu atakujący może uzyskać nieautoryzowany dostęp do systemu użytkownika za sprawą wyłączenia niektórych zabezpieczeń.

Preload

Teraz zajmiemy się integracją za pomocą skryptu preload. W odróżnieniu od powyższych, aby to rozwiązanie było możliwe, wystarczy że dezaktywujemy pojedynczą flagę odpowiadającą za izolację kontekstu:

  • contextIsolation: false

Wewnątrz samego skryptu definiujemy API, które będzie dostępne w współdzielonym obiekcie window w naszej aplikacji.

Definicja skryptu preload rozszerzającego wspólny obiekt window.

Pozostało nam nic innego jak wywoływać odpowiednie metody w naszym serwisie:

Definicja serwisu wywołującego metody na obiekcie window.

Aby pozostać w zgodzie z naszym sumieniem, utworzone zostały interfejsy reprezentujące “nowy”, rozszerzony obiekt window.

Interfejsy typujące nowy obiekt window.

Jeśli chodzi natomiast o kontroler po stronie naszego backendu, wszystko pozostaje bez zmian.

W tym rozwiązaniu skorzystaliśmy z zalecanego rozwiązania jeśli chodzi o komunikację między procesami Electronowymi. W dalszym jednak ciągu nasza aplikacja nie jest całkowicie odporna na podatności.

PostMessage API

Alternatywnym sposobem na wykorzystanie skryptu preload jest użycie PostMessage API. Wówczas zachowujemy domyślną konfigurację naszej aplikacji Electronowej, zalecanej przez electron-secure-defaults.

Przechodząc bezpośrednio do implementacji, sprowadza się ona do modyfikacji skryptu preload:

Implementacja skryptu preload z wykorzystaniem PostMessage API.

Oraz serwisu wysyłającego wiadomości:

Serwis wysyłający wiadomości do maina za pośrednictwem PostMessage API.

Wadą tego rozwiązania jest to, że nie jesteśmy w stanie przeprowadzić komunikacji w sposób synchroniczny. Ponadto, podczas wysyłania wiadomości musimy podać wartość * jako parametr targetOrigin, co samo w sobie może powodować luki w bezpieczeństwie.

Przejdźmy zatem do rozwiązania za pomocą którego nie musimy iść na żadne kompromisy w kwestii bezpieczeństwa oraz które zapewnia nam również synchroniczną komunikację.

Context Bridge

Rekomendowanym podejściem jest wykorzystanie tzw. Context Bridge’a w ramach skryptu preload w celu zapewnienia komunikacji między procesami Electronowymi.

Implementacja ponownie sprowadza się do odpowiedniej definicji API dla naszej aplikacji w ramach skryptu preload, z tą różnicą że nie rozszerzamy bezpośrednio obiektu window ale wykorzystujemy wspomniany wcześniej Context Bridge.

Definicja API przy użyciu Context Bridge.

Serwis umożliwiający nam komunikację wygląda identycznie jak w przypadku bezpośredniego rozszerzania obiektu window.

Rozwiązanie to jest aktualnie jednym z najbezpieczniejszych sposobów realizacji omawianej komunikacji. Istnieje natomiast jeszcze jedno podejście, które jest bardziej elastyczne i polega na całkowitym porzuceniu komunikacji przy użyciu IPC.

Lokalny serwer

Ostatnim dostępnym rozwiązaniem jest postawienie lokalnego serwera. Wówczas z poziomu naszej aplikacji komunikacja odbywa się za pomocą wybranego protokołu tak jakby to było w przypadku zdalnego serwera.

W tym rozwiązaniu mamy dowolność jeśli chodzi o wybór technologii, istotne natomiast jest to, aby wziąć pod uwagę solidne zabezpieczenie naszego serwera.

Jeśli chodzi o technologię oczywiście korzystamy z NestJs. Skonfigurujmy zatem nasz kontroler:

Kontroler definiujący API.

Implementacja serwisu pozostaje praktycznie taka sama oprócz minimalnych zmian w sygnaturach funkcji (brak eventu związanego z IPC).

Od strony aplikacji natomiast po prostu wysyłamy żądania pod wybrany adres:

Serwis wysyłający żądania do lokalnego serwera.

Niewątpliwymi zaletami tego rozwiązania jest elastyczność w doborze technologii naszego backendu jak i możliwość łatwej ewentualnej jego podmiany. Jak wielokrotnie wspominaliśmy, istotne jest odpowiednie zabezpieczenie naszego serwera.

Podsumowanie

Integracja istniejącej aplikacji z Electronem jest stosunkowa prosta, musimy być jednak świadomi że istnieją potencjalne luki w bezpieczeństwie takich aplikacji. Stosując jednak wszystkie zalecane zabezpieczenia, nasza aplikacja z pewnością będzie bezpieczna.

Kod źródłowy naszej przykładowej aplikacji znajdziecie w repozytorium, a przełączając się między branchami możecie zweryfikować jak sprawdza się dane rozwiązanie.

Mamy zatem nadzieję, że zastosujecie zdobytą tutaj wiedzę w praktyce i stworzycie nowe, bezpieczne aplikacje Electronowe z użyciem Angulara.

Przydatne linki

  1. Electron remote module considered harmful, https://nornagon.medium.com/electrons-remote-module-considered-harmful-70d69500f31
  2. Awesome ElectronJs hacking repository, https://github.com/doyensec/awesome-electronjs-hacking
  3. Electronegativity, https://github.com/doyensec/electronegativity
  4. Electron secure defaults, https://github.com/1Password/electron-secure-defaults
  5. Security checklist, https://www.electronjs.org/docs/tutorial/security#checklist-security-recommendations
  6. Sandbox, https://www.electronjs.org/docs/tutorial/sandbox
  7. Session, https://www.electronjs.org/docs/api/session
  8. Electron IPC and NodeIntegration, https://stackoverflow.com/questions/52236641/electron-ipc-and-nodeintegration

O autorze

Marcin Leśniczek

Wiecznie głodny wiedzy z pasją dla aplikacji mobilnych oraz hybrydowych. Zawsze otwarty na nowe pomysły i technologie, szukający dziury w całym. Po godzinach entuzjasta nauki i astronomii.

Chcesz razem z nami tworzyć treści na bloga? Dołącz do nas i twórz wartościowe treści dla sympatyków Angulara z Angular.love!

0 komentarzy

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *