OpenTelemetry w OpenEdge – część I

OpenTelemetry pojawiło się w OpenEdge 12.8. Wraz z tą wersją Progress wprowadził tracing zarówno dla ABL Client, jak i PASOE.

Jest to technologia coraz częściej pojawiająca się w środowiskach, w których chcemy wiedzieć nie tylko czy aplikacja działa, ale również co dokładnie robi i gdzie “spędza” najwięcej czasu.

W przypadku aplikacji napisanych w ABL jest to szczególnie interesujące. Przez lata do diagnozowania problemów z aplikacją OpenEdge wykorzystywaliśmy między innymi logi, czy narzędzia diagnostyczne jak Profiler. OpenTelemetry otwiera jednak trochę inną możliwość – możemy spojrzeć na wykonanie aplikacji z perspektywy trace’ów i spanów. I tak, trace opisuje wykonanie całej operacji użytkownika, a spany pokazują poszczególne elementy tego wykonania – np. ABL Client, PASOE, poszczególne procedury itp.

W OpenEdge Progress udostępnia natywne wsparcie dla OpenTelemetry w ABL Client. Nie trzeba więc od razu przebudowywać aplikacji ani dodawać własnego mechanizmu logowania. Tracing można włączyć na poziomie sesji ABL za pomocą parametru -otelconfig. Architektura śledzenia jest dość prosta.

ABL Client → OTLP → OpenTelemetry Collector → APM
ABL Client generuje dane telemetryczne i przekazuje je do OpenTelemetry Collector za pomocą protokołu OTLP. Collector może następnie przekazać je do wybranego narzędzia APM (Application Performance Monitor) np. Elastic APM, Dynatrace czy New Relic.

Na potrzeby pierwszego testu możemy jednak pominąć cały backend APM i instalację Collectora.
W OpenEdge mamy bowiem ostream, który Progress opisuje jako rozwiązanie przydatne właśnie do testowania i debugowania. Zamiast wysyłać trace do Collectora, możemy zapisać je bezpośrednio do pliku.
ABL Client → ostream → plik

Do testowania potrzebna będzie nam aplikacja ABL. Weźmy poniższy przykład składający się z czterech procedur, każda w osobnym pliku. Tylko procedura UpBalance.p generuje transakcję.

// CustomerOrders.p
VAR INTEGER iCustomer = 1, iOrders.
VAR CHARACTER cName.
    
RUN GetCustomer.p (iCustomer, OUTPUT cName).

RUN GetOrdersNum.p (iCustomer, OUTPUT iOrders).

RUN UpBalance.p (iCustomer).

MESSAGE
   cName iOrders
      VIEW-AS ALERT-BOX INFO. 
	  
// GetCustomer.p
DEFINE INPUT PARAMETER piCustNum AS INTEGER NO-UNDO.
DEFINE OUTPUT PARAMETER cName AS CHAR NO-UNDO.

FIND FIRST Customer
    WHERE Customer.CustNum = piCustNum NO-LOCK NO-ERROR.

IF AVAILABLE Customer THEN
    cName = NAME.
ELSE      
    cName = "".

// GetOrdersNum.p
DEFINE INPUT  PARAMETER piCustNum AS INTEGER NO-UNDO.
DEFINE OUTPUT PARAMETER piOrders  AS INTEGER NO-UNDO.
piOrders = 0.

FOR EACH Order WHERE Order.CustNum = piCustNum NO-LOCK:
   piOrders = piOrders + 1.
END.

// UpBalance.p
DEFINE INPUT PARAMETER piCustNum AS INTEGER NO-UNDO.

FIND FIRST Customer
    WHERE Customer.CustNum = piCustNum NO-ERROR.

IF AVAILABLE Customer THEN
    Balance = Balance * 1.1.

Konfiguracja OpenTelemetry jest przechowywana w pliku JSON. Zawiera ona m.in. informacje o exporterze (element OpenTelemetry odpowiedzialny za wysłanie (eksport) zebranych danych telemetrycznych) oraz zakresie śledzenia procedur ABL.

Plik konfiguracyjny przekazujemy do sesji ABL podczas jej uruchamiania za pomocą parametru -otelconfig, wskazując ścieżkę do pliku JSON.
Poniżej widać przykładowy plik konfiguracyjny myOtelConfig.json.

{
    "OpenTelemetryConfiguration": {
        "resource_attributes": "service.name=Sports2000",
        "exporters": {
            "ostream": [
                {
                    "filename": "C:\\WrkOpenEdge128\\otel\\traces.out",
                    "span_processor": "simple"
                }
            ]
        }
    },
    "OpenEdgeTelemetryConfiguration": {
        "trace_procedures": "*",
        "trace_classes": "*",
        "trace_abl_transactions": true,
        "trace_requires_parent": false,
        "trace_request_start": true
    }
}

Uwaga! dla trace_procedures i trace_classes po znaku dziekiej karty * nie może występować żaden ciąg znaków, czyli “Customer*” jest OK, ale “Customer*.p” już jest błędem.
“trace_requires_parent”: false – oznacza: jeżeli nie ma jeszcze nadrzędnego spanu (parent), i tak utwórz span. W przeciwnym razie, dla wartości true oznaczałoby: twórz spany tylko wtedy, gdy istnieje odpowiedni trace/span nadrzędny.

Sesję startuję poleceniem:
prowin ../db/sports2000 -otelconfig myOtelConfig.json
Gdyby pojawiły się błędy lub np. plik wynikowy był pusty, warto dodać parametry generujące log klienta:
prowin ../db/sports2000 -otelconfig myOtelConfig.json -clientlog otel.log -logentrytypes TELEMETRY -logginglevel 3

Plik wynikowy traces.out zawiera informacje o utworzonych spanach. Każdy span posiada między innymi trace_id, który identyfikuje cały ślad wykonania, oraz span_id i parent_span_id, pozwalające określić zależności pomiędzy poszczególnymi operacjami.

Pole name zawiera nazwę śledzonej procedury lub operacji. start określa moment rozpoczęcia spanu, natomiast duration informuje o czasie jego wykonania. W naszym przykładzie wartości czasu podawane są w nanosekundach.

W danych znajdziemy również informacje o rodzaju spanu (span kind), jego statusie oraz atrybutach. W przypadku transakcji OpenEdge możemy zobaczyć dodatkowe informacje, takie jak numer linii czy nazwa bazy danych.

Sekcja resources zawiera informacje opisujące źródło telemetryki. W naszym przypadku jest to między innymi service.name, ustawione w pliku konfiguracyjnym JSON, oraz informacje o wykorzystanej implementacji OpenTelemetry.

Dzięki tym informacjom pojedynczy plik wynikowy zawiera nie tylko listę wykonanych operacji, ale również dane pozwalające odtworzyć ich wzajemne zależności i zmierzyć czas wykonania poszczególnych elementów aplikacji.

W pliku wynikowym znajdziemy również informacje dotyczące transakcji ABL. W naszym przykładzie wykonanie procedury UpBalance.p powoduje utworzenie dodatkowego spanu BeginTransaction_UpBalance.p. Dzięki temu możemy zobaczyć nie tylko czas wykonania samej procedury, ale również czas związany z rozpoczęciem i obsługą transakcji.

Span transakcji może zawierać dodatkowe atrybuty, takie jak numer linii, w której rozpoczęła się transakcja (Line), oraz lista baz danych uczestniczących w operacji (dblist). Pozwala to powiązać dane telemetryczne z konkretnym fragmentem kodu ABL i używaną bazą danych.

Poniżej znajduje się rzeczywisty plik wyniowy traces.out wygenerowany podczas wykonania naszego przykładu. Dodam, że testy przeprowadziłem w środowisku OE 12.8 i 13.0 z podobnym rezultatem.

{
  name          : GetCustomer.p
  trace_id      : b391af61034180280d0c8ac7f847c547
  span_id       : 46814552c7b7f49d
  tracestate    : 
  parent_span_id: dba1567c11e6fce7
  start         : 1787993179723773800
  duration      : 31300
  description   : 
  span kind     : Internal
  status        : Unset
  attributes    : 
  events        : 
  links         : 
  resources     : 
	service.name: Sports2000
	telemetry.sdk.language: cpp
	telemetry.sdk.name: opentelemetry
	telemetry.sdk.version: 1.9.1
  instr-lib     : 54208-1.9.1
}
{
  name          : GetOrdersNum.p
  trace_id      : b391af61034180280d0c8ac7f847c547
  span_id       : d143669da300cb61
  tracestate    : 
  parent_span_id: dba1567c11e6fce7
  start         : 1787993179724423100
  duration      : 74600
  description   : 
  span kind     : Internal
  status        : Unset
  attributes    : 
  events        : 
  links         : 
  resources     : 
	service.name: Sports2000
	telemetry.sdk.language: cpp
	telemetry.sdk.name: opentelemetry
	telemetry.sdk.version: 1.9.1
  instr-lib     : 54208-1.9.1
}
{
  name          : BeginTransaction_UpBalance.p
  trace_id      : b391af61034180280d0c8ac7f847c547
  span_id       : 47fbd8c581af2e2c
  tracestate    : 
  parent_span_id: f2489adf6e462ba0
  start         : 1787993179724894000
  duration      : 887200
  description   : 
  span kind     : Internal
  status        : Unset
  attributes    : 
	Line: 1
	dblist: sports2000
  events        : 
  links         : 
  resources     : 
	service.name: Sports2000
	telemetry.sdk.language: cpp
	telemetry.sdk.name: opentelemetry
	telemetry.sdk.version: 1.9.1
  instr-lib     : 54208-1.9.1
}
{
  name          : UpBalance.p
  trace_id      : b391af61034180280d0c8ac7f847c547
  span_id       : f2489adf6e462ba0
  tracestate    : 
  parent_span_id: dba1567c11e6fce7
  start         : 1787993179724885700
  duration      : 930800
  description   : 
  span kind     : Internal
  status        : Unset
  attributes    : 
  events        : 
  links         : 
  resources     : 
	service.name: Sports2000
	telemetry.sdk.language: cpp
	telemetry.sdk.name: opentelemetry
	telemetry.sdk.version: 1.9.1
  instr-lib     : 54208-1.9.1
}
{
  name          : C:\WrkOpenEdge128\otel\p78729_CustomerOrders.ped
  trace_id      : b391af61034180280d0c8ac7f847c547
  span_id       : dba1567c11e6fce7
  tracestate    : 
  parent_span_id: 0000000000000000
  start         : 1787993179722973700
  duration      : 3154827200
  description   : 
  span kind     : Internal
  status        : Unset
  attributes    : 
  events        : 
  links         : 
  resources     : 
	service.name: Sports2000
	telemetry.sdk.language: cpp
	telemetry.sdk.name: opentelemetry
	telemetry.sdk.version: 1.9.1
  instr-lib     : 54208-1.9.1
}

W powyższym pliku widać, że wszystkie spany należące do jednego wykonania aplikacji mają ten sam trace_id (b391af61034180280d0c8ac7f847c547). Jest to identyfikator całego śladu (trace), dzięki któremu można powiązać ze sobą poszczególne operacje składające się na jedno wykonanie programu. W naszym przykładzie pozwala on powiązać CustomerOrders.ped z wywołanymi przez nią procedurami GetCustomer.p, GetOrdersNum.p oraz UpBalance.p.

W następnym odcinku, napiszę jak włączyć w ten proces Otel Collector.

FIPS 140-3 w OpenEdge 13 – cześć II

W poprzedniej części artykułu skonfigurowaliśmy środowisko OpenEdge 13 do pracy w trybie FIPS 140-3. Pokazaliśmy jak zarejestrować katalog DLC w ESAM oraz jak właczyć/wyłaczyć i sprawdzić status FIPS za pomocą narzędzia POLMAN.

Samo włączenie FIPS jest jednak dopiero początkiem. Z punktu widzenia administratora najważniejsze pytanie brzmi:
co stanie się z działającą bazą danych i serwerem PASOE po włączeniu FIPS?
W tej części skupimy się właśnie na tych dwóch elementach środowiska.

OpenEdge RDBMS jest jednym z kluczowych komponentów OpenEdge obsługujących tryb FIPS. Dokumentacja Progress wymienia w tym kontekście między innymi Transparent Data Encryption (TDE), HSM oraz Auditing.
Nie oznacza to jednak, że każda istniejąca baza wymaga pewnej konwersji.
Przejście na FIPS należy traktować przede wszystkim jako zmianę zasad dotyczących używanych mechanizmów kryptograficznych.
Baza może nadal zawierać dane utworzone przed aktywacją FIPS, jednak elementy wykorzystujące starsze, niezatwierdzone mechanizmy kryptograficzne mogą wymagać aktualizacji. O tych przestarzałych algorytmach wspomniałem w poprzednim artykule dotyczącym Transparent Data Encryption. Baza danych musi być zatrzymana i wystartowana ponownie po włączeniu FIPS.

Rozpocznijmy od sprawdzenia statusu i włączenia trybu FIPS.

Posłużę się teraz bazą mytde, którą utworzyłem w poprzednim artykule. Baza ma włączone szyfrowanie TDE. Można to sprawdzić na różne sposoby, np. komendą proutil describe.

Startujemy serwer bazy z podaniem hasła -Passphrase. Od razu widać informację odnośnie trybu FIPS.

Po ponownym uruchomieniu bazy warto sprawdzić:
– log bazy danych .lg
– możliwość połączenia aplikacji
– możliwość zalogowania użytkowników (ABL, SQL)
– działanie mechanizmów TDE/HSM (jeśli są zaimplementowane)
– ewentualne błędy dotyczące algorytmów kryptograficznych.

Przy migracji istniejącej bazy do OpenEdge 13 należy zwrócić szczególną uwagę na hasła użytkowników. Jest to istotne zwłaszcza wtedy, gdy baza pochodzi z OpenEdge 12.8 lub wcześniejszej wersji.
OpenEdge 13 wprowadza bezpieczniejszy mechanizm generowania hashy haseł. Nowo tworzone hasła są generowane przy użyciu funkcji GENERATE-PASSWORD-HASH, która obsługuje algorytmy zatwierdzone przez National Institute of Standards and Technology (NIST). Starsze hasła mogły natomiast zostać zapisane przy użyciu funkcji ENCODE().
Progress udostępnił w OpenEdge 13 prosty sposób na sprawdzenie, którzy użytkownicy posiadają jeszcze hasła utworzone przy użyciu starszego mechanizmu.

W Data Administration Tool należy wybrać:
Admin → Security → User Report

Wygenerowany raport zawiera między innymi kolumnę: New Pwd?
Yes – hasło zostało wygenerowane za pomocą GENERATE-PASSWORD-HASH;
No – hasło zostało zakodowane za pomocą starszej funkcji ENCODE().

Przykładowo raport może wyglądać następująco:

User ID       User Name             Pwd?    SQL only    New Pwd?
-----------------------------------------------------------------
admin         Administrator         yes     no          yes
user1         Jan Kowalski          yes     no          no
user2         Anna Nowak            yes     yes         yes

W takim przypadku użytkownik user1 wymaga szczególnej uwagi przed włączeniem FIPS, ponieważ jego hasło zostało zapisane przy użyciu starszego mechanizmu ENCODE(). MOżna to łatwo naprawić ustawiając nowe hasło,

Po włączeniu FIPS zaleca się również sprawdzenie logów agenta PASOE.
Jest to szczególnie istotne dlatego, że część problemów nie musi pojawić się podczas samego uruchamiania bazy. Mogą zostać ujawnione dopiero wtedy, gdy aplikacja wykona operację wymagającą konkretnego algorytmu kryptograficznego.

Drugim kluczowym elementem środowiska OpenEdge jest PASOE. Przed włączeniem trybu FIPS należy zatrzymać a następnie wyrejestrować wszystkie działające instancje poleceniem:
pasman unregister nazwa-instancji.
Po włączeniu FIPS i zrestartowaniu baz danych rejestrujemy ponownie instancje poleceniem:
pasman register nazwa-instancji ścieżka.
Po zarejestrowaniu instancji PASOE w środowisku FIPS należy zwrócić uwagę na jeszcze jeden element konfiguracji. Dokumentacja jasno wskazuje, że aby identyfikatory sesji generowane podczas Basic Authentication korzystały z algorytmów zatwierdzonych przez FIPS, należy odkomentować sekcję Valve w pliku: webapps//META-INF/context.xml
np. C:\WrkOpenEdge130\oepas1\webapps\ROOT\META-INF\context.xml.

Kolejnym krokiem jest ponowne wygenerowanie istniejących domain keystores (magazynów kluczy domeny) PASOE za pomocą narzędzia gendomreg. Progress wskazuje, że należy wykonać tę operację, aby istniejące keystore’y korzystały z modułów kryptograficznych pracujących w trybie FIPS.
Jest to szczególnie istotne w przypadku istniejących instancji PASOE, których domain keystore został utworzony przed włączeniem FIPS. Nie należy zakładać, że sama zmiana globalnej polityki ESAM automatycznie przygotuje istniejący keystore do pracy w trybie FIPS.
Ostatnim krokiem jest restart instancji PASOE i sprawdzenie logów czy nie ma w nich błędów.